From e87727dd111a5370a064e675b9b7c966cb84dfb0 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 22 Jul 2019 15:46:53 -0400 Subject: fixed link following to video frames. --- src/client/views/nodes/DocumentView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e0975f7bd..9429f0d78 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -351,7 +351,7 @@ export class DocumentView extends DocComponent(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"); -- cgit v1.2.3-70-g09d2 From c351d0d4f6f4e8492cade7d3056e516178f4a138 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 22 Jul 2019 17:08:25 -0400 Subject: fixed seeking for html video player element --- .../views/collections/CollectionVideoView.tsx | 40 +++++++++++++++++----- src/client/views/nodes/VideoBox.tsx | 1 + 2 files changed, 33 insertions(+), 8 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index d7d5773ba..31a8a93e0 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -7,6 +7,8 @@ import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from ". import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import "./CollectionVideoView.scss"; import React = require("react"); +import { InkingControl } from "../InkingControl"; +import { InkTool } from "../../../new_fields/InkField"; @observer @@ -19,18 +21,19 @@ export class CollectionVideoView extends React.Component { private get uIButtons() { let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); let curTime = NumCast(this.props.Document.curPage); - return (VideoBox._showControls ? [] : [ -
- {"" + Math.round(curTime)} - {" " + Math.round((curTime - Math.trunc(curTime)) * 100)} -
, + return ([
+ {"" + Math.round(curTime)} + {" " + Math.round((curTime - Math.trunc(curTime)) * 100)} +
, + VideoBox._showControls ? (null) : [
{this._videoBox && this._videoBox.Playing ? "\"" : ">"}
,
F
- ]); + + ]]); } @action @@ -53,12 +56,33 @@ export class CollectionVideoView extends React.Component { } } + _isclick = 0; @action - onResetDown = () => { + onResetDown = (e: React.PointerEvent) => { if (this._videoBox) { this._videoBox.Pause(); - this.props.Document.curPage = 0; + e.stopPropagation(); + this._isclick = 0; + document.addEventListener("pointermove", this.onPointerMove, true); + document.addEventListener("pointerup", this.onPointerUp, true); + InkingControl.Instance.switchTool(InkTool.Eraser); + } + } + + @action + onPointerMove = (e: PointerEvent) => { + this._isclick += Math.abs(e.movementX) + Math.abs(e.movementY); + if (this._videoBox) { + this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.curPage, 0) + Math.sign(e.movementX) * 0.0333)); } + e.stopImmediatePropagation(); + } + @action + onPointerUp = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.onPointerMove, true); + document.removeEventListener("pointerup", this.onPointerUp, true); + InkingControl.Instance.switchTool(InkTool.None); + this._isclick < 10 && (this.props.Document.curPage = 0); } setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; }; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 83ad2a3b3..5624d41a9 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -65,6 +65,7 @@ export class VideoBox extends DocComponent(VideoD @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) => { -- cgit v1.2.3-70-g09d2 From de46e5c8a6b484f56a18df53dded58697d6efa51 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 22 Jul 2019 17:30:20 -0400 Subject: detailed view toggle on return key pressed --- src/client/views/GlobalKeyHandler.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/client/views') diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index f378b6c0c..e8a588e58 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -4,6 +4,7 @@ import { CollectionDockingView } from "./collections/CollectionDockingView"; import { MainView } from "./MainView"; import { DragManager } from "../util/DragManager"; import { action } from "mobx"; +import { Doc } from "../../new_fields/Doc"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo; @@ -82,6 +83,9 @@ export default class KeyManager { }); }, "delete"); break; + case "enter": + SelectionManager.SelectedDocuments().map(selected => Doc.ToggleDetailLayout(selected.props.Document)); + break; } return { -- cgit v1.2.3-70-g09d2 From 010c7978ce19392b4f02bdea29e734ac2abac01f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 23 Jul 2019 01:33:14 -0400 Subject: further non-breaking cognitive services --- src/client/cognitive_services/CognitiveServices.ts | 8 ++++---- src/client/views/nodes/ImageBox.tsx | 18 +++++++++++++----- src/server/index.ts | 5 ++--- 3 files changed, 19 insertions(+), 12 deletions(-) (limited to 'src/client/views') diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 2a3fe1d26..d525455cb 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -8,8 +8,8 @@ import { RouteStore } from "../../server/RouteStore"; import { Utils } from "../../Utils"; export enum Services { - ComputerVision, - Face + ComputerVision = "vision", + Face = "face" } export enum Confidence { @@ -38,8 +38,8 @@ export namespace CognitiveServices { export const analyze = async (imageUrl: string, service: Services) => { return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => { let apiKey = await response.text(); - if (apiKey) { - return; + if (!apiKey) { + return undefined; } let uriBase; let parameters; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index c3ee1e823..f88150d66 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -25,6 +25,7 @@ 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'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl, Howler } = require('howler'); @@ -195,10 +196,10 @@ export class ImageBox extends DocComponent(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 +213,14 @@ export class ImageBox extends DocComponent(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 }); } } diff --git a/src/server/index.ts b/src/server/index.ts index d808e8158..5b086a2cf 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -286,12 +286,11 @@ addSecureRoute( addSecureRoute(Method.GET, (user, res, req) => { let requested = req.params.requestedservice; - console.log(requested); switch (requested) { - case "Face": + case "face": res.send(process.env.FACE); break; - case "ComputerVision": + case "vision": res.send(process.env.VISION); break; default: -- cgit v1.2.3-70-g09d2 From 767901272eeea30137330e710cc8cf70fd34f1de Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 23 Jul 2019 03:13:08 -0400 Subject: autotag based on confidence, allow concealment of computed fields that resolve to undefined with namespace constant ComputedField.undefined --- src/client/cognitive_services/CognitiveServices.ts | 11 ++++-- src/client/views/nodes/FaceRectangle.tsx | 29 ++++++++++++++ src/client/views/nodes/FaceRectangles.tsx | 46 ++++++++++++++++++++++ src/client/views/nodes/ImageBox.tsx | 2 + src/client/views/nodes/KeyValueBox.tsx | 2 +- src/new_fields/ScriptField.ts | 2 + 6 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 src/client/views/nodes/FaceRectangle.tsx create mode 100644 src/client/views/nodes/FaceRectangles.tsx (limited to 'src/client/views') diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index d525455cb..04a21c837 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -6,6 +6,8 @@ import { List } from "../../new_fields/List"; import { Docs } from "../documents/Documents"; import { RouteStore } from "../../server/RouteStore"; import { Utils } from "../../Utils"; +import { CompileScript } from "../util/Scripting"; +import { ComputedField } from "../../new_fields/ScriptField"; export enum Services { ComputerVision = "vision", @@ -107,12 +109,13 @@ export namespace CognitiveServices { let converter = (results: any) => { let tagDoc = new Doc; results.tags.map((tag: Tag) => { - if (tag.confidence >= +threshold) { - tagDoc[tag.name] = tag.confidence; - } + let sanitized = tag.name.replace(" ", "_"); + let script = `return (${tag.confidence} >= this.confidence) ? ${tag.confidence} : ${ComputedField.undefined}`; + let computed = CompileScript(script, { params: { this: "Doc" } }); + computed.compiled && (tagDoc[sanitized] = new ComputedField(computed)); }); tagDoc.title = "Generated Tags"; - tagDoc.confidenceThreshold = threshold.toString(); + tagDoc.confidence = threshold; return tagDoc; }; analyzeDocument(target, Services.ComputerVision, converter, "generatedTags"); 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 ( +
+ ); + } + +} \ 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; +} + +@observer +export default class FaceRectangles extends React.Component { + + 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 ( +
+ {templates.map(rectangle => )} +
+ ); + } + +} \ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index f88150d66..0f60bd0fb 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -26,6 +26,7 @@ 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'); @@ -379,6 +380,7 @@ export class ImageBox extends DocComponent(ImageD style={{ color: [DocListCast(this.extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={faFileAudio} size="sm" />
{/* {this.lightbox(paths)} */} + ); } } \ 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 { 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/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts index e8a1ea28a..e5ec34f57 100644 --- a/src/new_fields/ScriptField.ts +++ b/src/new_fields/ScriptField.ts @@ -107,6 +107,8 @@ export namespace ComputedField { useComputed = true; } + export const undefined = "__undefined"; + export function WithoutComputed(fn: () => T) { DisableComputedFields(); try { -- cgit v1.2.3-70-g09d2 From 97776a0f1725de5b512b803497af0801141c7f73 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 23 Jul 2019 12:30:52 -0400 Subject: switched native video to ontimeupdate instead of a timer --- src/client/views/nodes/VideoBox.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 5624d41a9..30ad75000 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -59,7 +59,7 @@ export class VideoBox extends DocComponent(VideoD this.Playing = true; update && this.player && this.player.play(); update && this._youtubePlayer && this._youtubePlayer.playVideo(); - !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); + this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); this.updateTimecode(); } @@ -72,7 +72,7 @@ export class VideoBox extends DocComponent(VideoD 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(); } @@ -114,6 +114,7 @@ export class VideoBox extends DocComponent(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, () => -- cgit v1.2.3-70-g09d2