aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts90
-rw-r--r--src/client/documents/Documents.ts3
-rw-r--r--src/client/views/InkingCanvas.tsx4
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx24
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx20
-rw-r--r--src/client/views/nodes/DocumentView.tsx1
-rw-r--r--src/client/views/nodes/FieldView.tsx3
-rw-r--r--src/client/views/presentationview/PresentationView.tsx13
9 files changed, 141 insertions, 19 deletions
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index d4085cf76..dcd27f858 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -8,10 +8,12 @@ import { RouteStore } from "../../server/RouteStore";
import { Utils } from "../../Utils";
import { CompileScript } from "../util/Scripting";
import { ComputedField } from "../../new_fields/ScriptField";
+import { InkData } from "../../new_fields/InkField";
export enum Services {
ComputerVision = "vision",
- Face = "face"
+ Face = "face",
+ Handwriting = "handwriting"
}
export enum Confidence {
@@ -132,4 +134,90 @@ export namespace CognitiveServices {
}
+ export namespace Inking {
+
+ export interface AzureStrokeData {
+ id: number;
+ points: string;
+ language?: string;
+ }
+
+ export interface HandwritingUnit {
+ version: number;
+ language: string;
+ unit: string;
+ strokes: AzureStrokeData[];
+ }
+
+ export const analyze = async (inkData: InkData, target: Doc) => {
+ return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${Services.Handwriting}`)).then(async response => {
+ let apiKey = await response.text();
+ if (!apiKey) {
+ return undefined;
+ }
+
+ let xhttp = new XMLHttpRequest();
+ let serverAddress = "https://api.cognitive.microsoft.com";
+ let endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
+
+ let requestExecutor = (resolve: any, reject: any) => {
+ let result: any;
+ let body = format(inkData);
+
+ xhttp.onreadystatechange = function () {
+ if (this.readyState === 4) {
+ try {
+ result = JSON.parse(xhttp.responseText);
+ } catch (e) {
+ return reject(e);
+ }
+ switch (this.status) {
+ case 200:
+ return resolve(result);
+ case 400:
+ default:
+ return reject(result);
+ }
+ }
+ };
+
+ xhttp.open("PUT", endpoint, true);
+ xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
+ xhttp.setRequestHeader('Content-Type', 'application/json');
+ xhttp.send(body);
+ };
+
+ let results = await new Promise<any>(requestExecutor);
+
+ if (results) {
+ results.recognitionUnits && (results = results.recognitionUnits);
+ target.inkAnalysis = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
+ let recognizedText = results.map((item: any) => item.recognizedText);
+ let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
+ target.handwriting = individualWords.join(" ");
+ }
+ });
+ };
+
+ const format = (inkData: InkData): string => {
+ let entries = inkData.entries(), next = entries.next();
+ let strokes: AzureStrokeData[] = [], id = 0;
+ while (!next.done) {
+ strokes.push({
+ id: id++,
+ points: next.value[1].pathData.map(point => `${point.x},${point.y}`).join(","),
+ language: "en-US"
+ });
+ next = entries.next();
+ }
+ return JSON.stringify({
+ version: 1,
+ language: "en-US",
+ unit: "mm",
+ strokes: strokes
+ });
+ };
+
+ }
+
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7563fda20..a5b1d6573 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -56,7 +56,8 @@ export enum DocumentType {
IMPORT = "import",
LINK = "link",
LINKDOC = "linkdoc",
- TEMPLATE = "template"
+ TEMPLATE = "template",
+ EXTENSION = "extension"
}
export interface DocumentOptions {
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 3e0d7b476..e48b45b56 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -10,6 +10,8 @@ import { undoBatch, UndoManager } from "../util/UndoManager";
import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
import { Doc } from "../../new_fields/Doc";
import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { ContextMenu } from "./ContextMenu";
interface InkCanvasProps {
getScreenTransform: () => Transform;
@@ -178,7 +180,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
render() {
let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect";
return (
- <div className="inkingCanvas" >
+ <div className="inkingCanvas">
<div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
{this.props.children()}
{this.drawnPaths}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 94a4835a1..53be0278e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -394,6 +394,7 @@ export class MainView extends React.Component {
<div id="add-options-content">
<ul id="add-options-list">
<li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
+ <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={() => PresentationView.Instance.toggle(undefined)}><FontAwesomeIcon icon="table" size="sm" /></button></li>
<li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
<li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
{btns.map(btn =>
@@ -444,7 +445,6 @@ export class MainView extends React.Component {
this.isSearchVisible = !this.isSearchVisible;
}
-
render() {
return (
<div id="main-div">
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 481fbd5c5..006de0c70 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -74,9 +74,10 @@ class TreeView extends React.Component<TreeViewProps> {
@observable _collapsed: boolean = true;
@computed get fieldKey() {
- let keys = Array.from(Object.keys(this.dataDoc)); // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
- if (this.dataDoc.proto instanceof Doc) {
- let arr = Array.from(Object.keys(this.dataDoc.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+ let target = this.props.document;
+ let keys = Array.from(Object.keys(target)); // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+ if (target.proto instanceof Doc) {
+ let arr = Array.from(Object.keys(target.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
keys.push(...arr);
while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
}
@@ -88,7 +89,7 @@ class TreeView extends React.Component<TreeViewProps> {
}
});
let layout = StrCast(this.props.document.layout);
- if (layout.indexOf("fieldKey={\"") !== -1) {
+ if (layout.indexOf("fieldKey={\"") !== -1 && layout.indexOf("fieldExt=") === -1) {
return layout.split("fieldKey={\"")[1].split("\"")[0];
}
return keyList.length ? keyList[0] : "data";
@@ -191,16 +192,17 @@ class TreeView extends React.Component<TreeViewProps> {
/>)
@computed get keyList() {
- let keys = Array.from(Object.keys(this.dataDoc));
- if (this.dataDoc.proto instanceof Doc) {
- keys.push(...Array.from(Object.keys(this.dataDoc.proto)));
+ let target = this.props.document;
+ let keys = Array.from(Object.keys(target));
+ if (target.proto instanceof Doc) {
+ keys.push(...Array.from(Object.keys(target.proto)));
}
let keyList: string[] = keys.reduce((l, key) => {
- let listspec = DocListCast(this.dataDoc[key]);
+ let listspec = DocListCast(target[key]);
if (listspec && listspec.length) return [...l, key];
return l;
}, [] as string[]);
- keys.map(key => Cast(this.dataDoc[key], Doc) instanceof Doc && (Cast(this.dataDoc[key], Doc) as Doc).type !== undefined && keyList.push(key));
+ keys.map(key => Cast(target[key], Doc) instanceof Doc && keyList.push(key));
if (LinkManager.Instance.getAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
if (keyList.indexOf(this.fieldKey) !== -1) {
keyList.splice(keyList.indexOf(this.fieldKey), 1);
@@ -361,10 +363,10 @@ class TreeView extends React.Component<TreeViewProps> {
let docList = Cast(this.dataDoc[this._chosenKey], listSpec(Doc));
let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this._chosenKey, doc, addBefore, before);
- let doc = Cast(this.dataDoc[this._chosenKey], Doc);
if (!this._collapsed) {
if (!this.props.document.embed) {
+ let doc = Cast(this.props.document[this._chosenKey], Doc);
contentElement = <ul key={this._chosenKey + "more"}>
{this._chosenKey === "links" ? this.renderLinks() :
TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.resolvedDataDoc, this._chosenKey, addDoc, remDoc, this.move,
@@ -446,7 +448,7 @@ class TreeView extends React.Component<TreeViewProps> {
dataDoc={dataDoc}
containingCollection={containingCollection}
treeViewId={treeViewId}
- key={child[Id] + "child " + i}
+ key={child[Id]}
indentDocument={indent}
renderDepth={renderDepth}
deleteDoc={remove}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7a9b94bfd..840f253ff 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -31,6 +31,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
import { OverlayView, OverlayElementOptions } from "../../OverlayView";
import { ScriptBox } from "../../ScriptBox";
import { CompileScript } from "../../../util/Scripting";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
export const panZoomSchema = createSchema({
@@ -51,6 +52,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private _lastY: number = 0;
private get _pwidth() { return this.props.PanelWidth(); }
private get _pheight() { return this.props.PanelHeight(); }
+ private inkKey = "ink";
@computed get contentBounds() {
let bounds = this.props.fitToBox && !NumCast(this.nativeWidth) ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
@@ -358,8 +360,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
let self = this;
let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
- resolvedDataDoc && Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
- let layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
+ let layoutDoc = childDocLayout;
+ if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
+ Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
+ let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
+ layoutDoc = Doc.expandTemplateLayout(childDocLayout, fieldExtensionDoc !== resolvedDataDoc ? fieldExtensionDoc : undefined);
+ } else layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
return {
DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined,
Document: layoutDoc,
@@ -478,8 +484,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}, "arrange contents");
}
});
+ ContextMenu.Instance.addItem({
+ description: "Analyze Strokes", event: async () => {
+ let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
+ if (!data) {
+ return;
+ }
+ CognitiveServices.Inking.analyze(data.inkData, Doc.GetProto(this.props.Document));
+ }
+ });
}
+
private childViews = () => [
<CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
...this.views
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 138d065c6..2b228f4e4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -297,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;
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/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
index f80840f96..b318f0321 100644
--- a/src/client/views/presentationview/PresentationView.tsx
+++ b/src/client/views/presentationview/PresentationView.tsx
@@ -31,6 +31,8 @@ export interface PresViewProps {
Documents: List<Doc>;
}
+const expandedWidth = 400;
+
@observer
export class PresentationView extends React.Component<PresViewProps> {
public static Instance: PresentationView;
@@ -67,6 +69,15 @@ export class PresentationView extends React.Component<PresViewProps> {
PresentationView.Instance = this;
}
+ @action
+ toggle = (forcedValue: boolean | undefined) => {
+ if (forcedValue !== undefined) {
+ this.curPresentation.width = forcedValue ? expandedWidth : 0;
+ } else {
+ this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth;
+ }
+ }
+
//The first lifecycle function that gets called to set up the current presentation.
async componentWillMount() {
this.props.Documents.forEach(async (doc, index: number) => {
@@ -543,7 +554,7 @@ export class PresentationView extends React.Component<PresViewProps> {
this.curPresentation.data = new List([doc]);
}
- this.curPresentation.width = 400;
+ this.toggle(true);
}
//Function that sets the store of the children docs.