From 810f86195188503b04d64f9d58ea4dfc3a639398 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 22 Mar 2022 11:32:47 -0400 Subject: fixed temporal media merge that had reverted a lot of things. --- src/client/documents/Documents.ts | 92 +++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 42 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index df573a377..9bcd23aa0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -23,7 +23,7 @@ import { DocumentManager } from "../util/DocumentManager"; import { dropActionType } from "../util/DragManager"; import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox"; import { LinkManager } from "../util/LinkManager"; -import { Scripting } from "../util/Scripting"; +import { ScriptingGlobals } from "../util/ScriptingGlobals"; import { undoBatch, UndoManager } from "../util/UndoManager"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; @@ -61,7 +61,6 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { MapBox } from "../views/nodes/MapBox/MapBox"; -const path = require('path'); const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); class EmptyBox { @@ -180,7 +179,7 @@ export class DocumentOptions { layout?: string | Doc; // default layout string for a document contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents childLimitHeight?: number; // whether to limit the height of collection children. 0 - means height can be no bigger than width - childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view) + childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view) childLayoutString?: string; // template string for collection to use to render its children childDontRegisterViews?: boolean; childHideLinkButton?: boolean; // hide link buttons on all children @@ -235,7 +234,7 @@ export class DocumentOptions { docColorBtn?: string; userColorBtn?: string; canClick?: string; - script?: string; + script?: ScriptField; numBtnType?: string; numBtnMax?: number; numBtnMin?: number; @@ -361,31 +360,34 @@ export namespace Docs { const TemplateMap: TemplateMap = new Map([ [DocumentType.RTF, { layout: { view: FormattedTextBox, dataField: "text" }, - options: { _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any } + options: { + _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, + links: "@links(self)" + } }], [DocumentType.SEARCH, { layout: { view: SearchBox, dataField: defaultDataKey }, - options: { _width: 400, links: ComputedField.MakeFunction("links(self)") as any } + options: { _width: 400, links: "@links(self)" } }], [DocumentType.FILTER, { layout: { view: FilterBox, dataField: defaultDataKey }, - options: { _width: 400, links: ComputedField.MakeFunction("links(self)") as any } + options: { _width: 400, links: "@links(self)" } }], [DocumentType.COLOR, { layout: { view: ColorBox, dataField: defaultDataKey }, - options: { _nativeWidth: 220, _nativeHeight: 300, links: ComputedField.MakeFunction("links(self)") as any } + options: { _nativeWidth: 220, _nativeHeight: 300, links: "@links(self)" } }], [DocumentType.IMG, { layout: { view: ImageBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.WEB, { layout: { view: WebBox, dataField: defaultDataKey }, - options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any } + options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" } }], [DocumentType.COL, { layout: { view: CollectionView, dataField: defaultDataKey }, - options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: ComputedField.MakeFunction("links(self)") as any } + options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: "@links(self)" } }], [DocumentType.KVP, { layout: { view: KeyValueBox, dataField: defaultDataKey }, @@ -393,19 +395,19 @@ export namespace Docs { }], [DocumentType.VID, { layout: { view: VideoBox, dataField: defaultDataKey }, - options: { _currentTimecode: 0, links: ComputedField.MakeFunction("links(self)") as any }, + options: { _currentTimecode: 0, links: "@links(self)" }, }], [DocumentType.AUDIO, { layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 100, backgroundColor: "lightGray", links: ComputedField.MakeFunction("links(self)") as any } + options: { _height: 100, backgroundColor: "lightGray", links: "@links(self)" } }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: defaultDataKey }, - options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any } + options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" } }], [DocumentType.MAP, { layout: { view: MapBox, dataField: defaultDataKey }, - options: { _height: 600, _width: 800, links: ComputedField.MakeFunction("links(self)") as any } + options: { _height: 600, _width: 800, links: "@links(self)" } }], [DocumentType.IMPORT, { layout: { view: DirectoryImportBox, dataField: defaultDataKey }, @@ -416,7 +418,7 @@ export namespace Docs { options: { childDontRegisterViews: true, _isLinkButton: true, _height: 150, description: "", showCaption: "description", backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area - links: ComputedField.MakeFunction("links(self)") as any, + links: "@links(self)", _removeDropProperties: new List(["_layerTags", "isLinkButton"]), } }], @@ -432,61 +434,61 @@ export namespace Docs { }], [DocumentType.SCRIPTING, { layout: { view: ScriptingBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.YOUTUBE, { layout: { view: YoutubeBox, dataField: defaultDataKey } }], [DocumentType.LABEL, { layout: { view: LabelBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.EQUATION, { layout: { view: EquationBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any, hideResizeHandles: true, hideDecorationTitle: true } + options: { links: "@links(self)", hideResizeHandles: true, hideDecorationTitle: true } }], [DocumentType.FUNCPLOT, { layout: { view: FunctionPlotBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.BUTTON, { layout: { view: LabelBox, dataField: "onClick" }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.SLIDER, { layout: { view: SliderBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.PRES, { layout: { view: PresBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.FONTICON, { layout: { view: FontIconBox, dataField: defaultDataKey }, - options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: "100%", links: ComputedField.MakeFunction("links(self)") as any }, + options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: "100%", links: "@links(self)" }, }], [DocumentType.WEBCAM, { layout: { view: DashWebRTCVideo, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.PRESELEMENT, { layout: { view: PresElementBox, dataField: defaultDataKey } }], [DocumentType.MARKER, { layout: { view: CollectionView, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true } + options: { links: "@links(self)", hideLinkButton: true } }], [DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method layout: { view: InkingStroke, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.SCREENSHOT, { layout: { view: ScreenshotBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.COMPARISON, { layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", links: ComputedField.MakeFunction("links(self)") as any } + options: { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", links: "@links(self)" } }], [DocumentType.GROUPDB, { data: new List(), @@ -495,7 +497,7 @@ export namespace Docs { }], [DocumentType.GROUP, { layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], ]); @@ -592,6 +594,11 @@ export namespace Docs { system: true, _layoutKey: "layout", title, type, baseProto: true, x: 0, y: 0, _width: 300, ...(template.options || {}), layout: layout.view?.LayoutString(layout.dataField), data: template.data, layout_keyValue: KeyValueBox.LayoutString("") }; + Object.entries(options).map(pair => { + if (typeof pair[1] === "string" && pair[1].startsWith("@")) { + (options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1)); + } + }); return Doc.assign(new Doc(prototypeId, true), options as any, undefined, true); } } @@ -655,7 +662,7 @@ export namespace Docs { export function ImageDocument(url: string, options: DocumentOptions = {}) { const imgField = new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: path.basename(url), ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options }); } export function PresDocument(initial: List = new List(), options: DocumentOptions = {}) { @@ -758,7 +765,7 @@ export namespace Docs { I.data = new InkField(points); I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; I["acl-Override"] = "None"; - I.links = ComputedField.MakeFunction("links(self)") as any; + I.links = "@links(self)"; I[Initializing] = false; return I; } @@ -1443,13 +1450,13 @@ export namespace DocUtils { if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; - proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""); + proto.fileUpload = pathname.replace(/.*\//, "").replace("upload_", "").replace(/\.[a-z0-9]*$/, ""); if (Upload.isImageInformation(result)) { const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); - proto["data-nativeOrientation"] = result.exifData?.data?.image?.Orientation; + proto["data-nativeOrientation"] = result.exifData?.data?.image?.Orientation ?? ((StrCast((result.exifData?.data as any)?.Orientation).includes("Rotate 90")) ? 5 : undefined); proto["data-nativeWidth"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim * result.nativeWidth / result.nativeHeight : maxNativeDim; proto["data-nativeHeight"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - if (Number(result.exifData?.data?.image?.Orientation) >= 5) { + if (NumCast(proto["data-nativeOrientation"]) >= 5) { 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); } @@ -1472,27 +1479,28 @@ export namespace DocUtils { export async function uploadYoutubeVideo(videoId: string, options: DocumentOptions) { const generatedDocuments: Doc[] = []; for (const { source: { name, type }, result } of await Networking.UploadYoutubeToServer(videoId)) { - processFileupload(generatedDocuments, name, type, result, options); + name && type && processFileupload(generatedDocuments, name, type, result, options); } return generatedDocuments; } export async function uploadFilesToDocs(files: File[], options: DocumentOptions) { const generatedDocuments: Doc[] = []; - for (const { source: { name, type }, result } of await Networking.UploadFilesToServer(files)) { - processFileupload(generatedDocuments, name, type, result, options); + const upfiles = await Networking.UploadFilesToServer(files); + for (const { source: { name, type }, result } of upfiles) { + name && type && processFileupload(generatedDocuments, name, type, result, options); } return generatedDocuments; } } -Scripting.addGlobal("Docs", Docs); -Scripting.addGlobal(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; }); -Scripting.addGlobal(function generateLinkTitle(self: Doc) { +ScriptingGlobals.add("Docs", Docs); +ScriptingGlobals.add(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; }); +ScriptingGlobals.add(function generateLinkTitle(self: Doc) { const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : ""; const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : ""; const relation = self.linkRelationship || "to"; return `${anchor1title} (${relation}) ${anchor2title}`; }); -Scripting.addGlobal(function openTabAlias(tab: Doc) { +ScriptingGlobals.add(function openTabAlias(tab: Doc) { CollectionDockingView.AddSplit(Doc.MakeAlias(tab), "right"); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From ab0e1ac6f5b21f2e10bdce428c882482a0f159b4 Mon Sep 17 00:00:00 2001 From: mehekj Date: Tue, 12 Apr 2022 18:09:03 -0400 Subject: ink to text for groupings --- package-lock.json | 79 ++++++++- package.json | 1 + src/client/documents/Documents.ts | 5 +- src/client/views/GestureOverlay.tsx | 16 +- src/client/views/InkTranscription.scss | 5 + src/client/views/InkTranscription.tsx | 188 +++++++++++++++++++++ src/client/views/MainView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 19 ++- .../collectionFreeForm/MarqueeOptionsMenu.tsx | 11 -- .../collections/collectionFreeForm/MarqueeView.tsx | 16 +- src/client/views/nodes/EquationBox.scss | 36 ++++ src/client/views/nodes/EquationBox.tsx | 91 +++++++++- src/fields/InkField.ts | 3 +- src/pen-gestures/GestureUtils.ts | 3 +- src/typings/index.d.ts | 1 + 15 files changed, 444 insertions(+), 34 deletions(-) create mode 100644 src/client/views/InkTranscription.scss create mode 100644 src/client/views/InkTranscription.tsx (limited to 'src/client/documents') diff --git a/package-lock.json b/package-lock.json index b1431e55b..1f8b1b8f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4579,6 +4579,16 @@ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, + "clipboard": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", + "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "cliss": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/cliss/-/cliss-0.0.2.tgz", @@ -5307,6 +5317,11 @@ } } }, + "crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -5885,6 +5900,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -7846,6 +7866,14 @@ "jquery": "*" } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "requires": { + "delegate": "^3.1.2" + } + }, "google-auth-library": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-4.2.6.tgz", @@ -8462,6 +8490,29 @@ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" }, + "iink-js": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/iink-js/-/iink-js-1.5.4.tgz", + "integrity": "sha512-WcjMmyXbSo4GY56AVVTKRyxlguh2KAGFyoH24+Zmm87ZWII12or2uLiovAyOK4IE+VVz7m0U5RMuv8i/RV/3Nw==", + "requires": { + "@babel/runtime": "^7.9.2", + "clipboard": "^1.7.1", + "crypto-js": "^3.3.0", + "d3-selection": "^1.4.1", + "json-css": "^1.5.6", + "lodash.merge": "^4.6.2", + "loglevel": "^1.6.8", + "perfect-scrollbar": "^1.5.0", + "uuid-js": "^0.7.5" + }, + "dependencies": { + "d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + } + } + }, "image-data-uri": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/image-data-uri/-/image-data-uri-2.0.1.tgz", @@ -9524,6 +9575,11 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, + "json-css": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/json-css/-/json-css-1.5.6.tgz", + "integrity": "sha1-65ZPg0ouTqobwvaY/12wB6JsfAA=" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -9996,8 +10052,7 @@ "loglevel": { "version": "1.6.8", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", - "dev": true + "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==" }, "loglevelnext": { "version": "1.0.5", @@ -14793,6 +14848,11 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, + "perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -16791,6 +16851,11 @@ "resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz", "integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio=" }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -18208,6 +18273,11 @@ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -19185,6 +19255,11 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, + "uuid-js": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/uuid-js/-/uuid-js-0.7.5.tgz", + "integrity": "sha1-bIhtAqU9LUDc8l2RoXC0p7JblNA=" + }, "valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", diff --git a/package.json b/package.json index 2c3e641dd..c8455c8a3 100644 --- a/package.json +++ b/package.json @@ -196,6 +196,7 @@ "https": "^1.0.0", "https-browserify": "^1.0.0", "i": "^0.3.7", + "iink-js": "^1.5.4", "image-data-uri": "^2.0.1", "image-size": "^0.7.5", "image-size-stream": "^1.1.0", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9bcd23aa0..884cc781d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -4,7 +4,7 @@ import { DateField } from "../../fields/DateField"; import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Initializing, Opt, updateCachedAcls, WidthSym } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { HtmlField } from "../../fields/HtmlField"; -import { InkField } from "../../fields/InkField"; +import { InkField, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; import { ProxyField } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; @@ -738,7 +738,7 @@ export namespace Docs { return linkDoc; } - export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { + export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: PointData[], options: DocumentOptions = {}) { const I = new Doc(); I[Initializing] = true; I.type = DocumentType.INK; @@ -1161,6 +1161,7 @@ export namespace DocUtils { created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; } else if (field instanceof InkField) { + console.log("Documents " + field.inkData) created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); layout = InkingStroke.LayoutString; } else if (field instanceof List && field[0] instanceof Doc) { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 50dca0a99..16a9cfaeb 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -41,6 +41,7 @@ export class GestureOverlay extends Touchable { @observable private _menuY: number = -300; @observable private _pointerY?: number; @observable private _points: { X: number, Y: number }[] = []; + @observable private _timeStamps: number[] = []; @observable private _strokes: InkData[] = []; @observable private _palette?: JSX.Element; @observable private _clipboardDoc?: JSX.Element; @@ -59,6 +60,7 @@ export class GestureOverlay extends Touchable { private pointerIdentifier?: number; private _hands: Map = new Map(); private _holdTimer: NodeJS.Timeout | undefined; + private _strokeStartTime: number = 0; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -418,6 +420,7 @@ export class GestureOverlay extends Touchable { this._strokes = []; this._points = []; + this._timeStamps = []; this._possibilities = []; document.removeEventListener("touchend", this.handleHandUp); } @@ -493,6 +496,8 @@ export class GestureOverlay extends Touchable { onPointerDown = (e: React.PointerEvent) => { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { this._points.push({ X: e.clientX, Y: e.clientY }); + this._timeStamps.push(0); + this._strokeStartTime = new Date().getTime(); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); } @@ -501,6 +506,7 @@ export class GestureOverlay extends Touchable { @action onPointerMove = (e: PointerEvent) => { this._points.push({ X: e.clientX, Y: e.clientY }); + this._timeStamps.push(new Date().getTime() - this._strokeStartTime); if (this._points.length > 1) { const B = this.svgBounds; @@ -547,6 +553,7 @@ export class GestureOverlay extends Touchable { const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); //push first points to so interactionUtil knows pointer is up this._points.push({ X: this._points[0].X, Y: this._points[0].Y }); + this._timeStamps.push(this._timeStamps[0]); const initialPoint = this._points[0.]; const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height); @@ -558,6 +565,7 @@ export class GestureOverlay extends Touchable { case ToolglassTools.InkToText: this._strokes.push(new Array(...this._points)); this._points = []; + this._timeStamps = []; CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => { const wordResults = results.filter((r: any) => r.category === "line"); const possibilities: string[] = []; @@ -581,6 +589,7 @@ export class GestureOverlay extends Touchable { case ToolglassTools.IgnoreGesture: this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; + this._timeStamps = []; break; } } @@ -589,6 +598,7 @@ export class GestureOverlay extends Touchable { this.makePolygon(this.InkShape, false); this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; + this._timeStamps = []; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { this.InkShape = ""; Doc.UserDoc().activeInkTool = InkTool.None; @@ -635,9 +645,11 @@ export class GestureOverlay extends Touchable { this.dispatchGesture(GestureUtils.Gestures.Stroke); } this._points = []; + this._timeStamps = []; } } else { this._points = []; + this._timeStamps = []; } CollectionFreeFormViewChrome.Instance?.primCreated(); } @@ -686,6 +698,7 @@ export class GestureOverlay extends Touchable { } } this._points = []; + this._timeStamps = []; switch (shape) { //must push an extra point in the end so InteractionUtils knows pointer is up. //must be (points[0].X,points[0]-1) @@ -808,7 +821,8 @@ export class GestureOverlay extends Touchable { points: stroke ?? this._points, gesture: gesture as any, bounds: this.getBounds(stroke ?? this._points), - text: data + text: data, + times: this._timeStamps } } ) diff --git a/src/client/views/InkTranscription.scss b/src/client/views/InkTranscription.scss new file mode 100644 index 000000000..bbb0a1afa --- /dev/null +++ b/src/client/views/InkTranscription.scss @@ -0,0 +1,5 @@ +.ink-transcription { + .error-msg { + display: none !important; + } +} \ No newline at end of file diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx new file mode 100644 index 000000000..950f622dd --- /dev/null +++ b/src/client/views/InkTranscription.tsx @@ -0,0 +1,188 @@ +import { timeStamp } from 'console'; +import * as iink from 'iink-js'; +import { action, observable } from 'mobx'; +import * as React from 'react'; +import { Doc } from '../../fields/Doc'; +import { InkData, InkField } from "../../fields/InkField"; +import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { Docs } from "../documents/Documents"; +import './InkTranscription.scss'; + +export class InkTranscription extends React.Component { + static Instance: InkTranscription; + + @observable _mathRegister: any; + @observable _mathRef: any; + @observable _textRegister: any; + @observable _textRef: any; + private addDocument?: (doc: Doc | Doc[]) => boolean; + private bounds: { x: number, y: number, width: number, height: number } = { x: 0, y: 0, width: 0, height: 0 }; + + constructor(props: Readonly<{}>) { + super(props); + + InkTranscription.Instance = this; + } + + componentWillUnmount() { + this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); + this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); + + this._mathRef.removeEventListener('changed', (e: any) => this.callExport(this._mathRef)); + this._textRef.removeEventListener('changed', (e: any) => this.callExport(this._textRef)); + } + + @action + setMathRef = (r: any) => { + if (!this._mathRegister) { + this._mathRegister = r ? iink.register(r, { + recognitionParams: { + type: 'MATH', + protocol: 'WEBSOCKET', + server: { + host: 'cloud.myscript.com', + applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f', + hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1', + websocket: { + pingEnabled: false, + autoReconnect: true + } + }, + iink: { + math: { + mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'] + }, + export: { + jiix: { + strokes: true + } + } + } + }, + triggers: { + exportContent: 'DEMAND' + } + }) : null; + } + + r.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); + r.addEventListener('changed', (e: any) => this.callExport(this._mathRef)); + + return this._mathRef = r; + } + + @action + setTextRef = (r: any) => { + if (!this._textRegister) { + this._textRegister = r ? iink.register(r, { + recognitionParams: { + type: 'TEXT', + protocol: 'WEBSOCKET', + server: { + host: 'cloud.myscript.com', + applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f', + hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1', + websocket: { + pingEnabled: false, + autoReconnect: true + } + }, + iink: { + text: { + mimeTypes: ['text/plain'] + }, + export: { + jiix: { + strokes: true + } + } + } + }, + triggers: { + exportContent: 'DEMAND' + } + }) : null; + } + + r.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); + r.addEventListener('changed', (e: any) => this.callExport(this._textRef)); + + return this._textRef = r; + } + + transcribeInk = (inkdocs: Doc[], math: boolean, bounds: { x: number, y: number, width: number, height: number }, addDocument?: (doc: Doc | Doc[]) => boolean) => { + const strokes: InkData[] = []; + inkdocs.filter(i => Cast(i.data, InkField)).forEach(i => { + // TODO: interpolate missing times stamps + const d = Cast(i.data, InkField, null); + const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]); + const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); + strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top, time: pd.time }))); + }); + + this.addDocument = addDocument; + this.bounds = bounds; + + const pointerData = { "events": strokes.map(stroke => this.inkJSON(stroke)) }; + // console.log(JSON.stringify(pointerData)); + const processGestures = false; + + if (math) { + this._mathRef.editor.pointerEvents(pointerData, processGestures); + } + else { + this._textRef.editor.pointerEvents(pointerData, processGestures); + } + } + + callExport(ref: any) { + if (ref.editor.canExport) { + ref.editor.export_(); + } + } + + inkJSON = (stroke: InkData) => { + return { + "pointerType": "PEN", + "pointerId": 1, + "x": stroke.map(point => point.X), + "y": stroke.map(point => point.Y), + "t": stroke.map(point => point.time), + "p": new Array(stroke.length).fill(1.0) + }; + } + + exportInk = (e: any, ref: any) => { + const exports = e.detail.exports; + if (exports) { + if (exports['application/x-latex']) { + const latex = exports['application/x-latex']; + console.log(latex); + + this.addDocument?.(Docs.Create.EquationDocument({ title: latex, x: this.bounds.width, y: 0, _width: this.bounds.width, _height: this.bounds.height })); + } + else if (exports['text/plain']) { + const text = exports['text/plain']; + console.log(text); + this.addDocument?.(Docs.Create.TextDocument(text, { title: text, x: this.bounds.width, y: 0, _width: this.bounds.width, _height: this.bounds.height })); + } + + ref.editor.clear(); + } + } + + render() { + return ( +
+
+
+
+
+
+ ) + } +} \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8c0795881..84c1037dd 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -44,6 +44,7 @@ import { GestureOverlay } from './GestureOverlay'; import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss'; import { Colors } from './global/globalEnums'; import { KeyManager } from './GlobalKeyHandler'; +import { InkTranscription } from './InkTranscription'; import { LightboxView } from './LightboxView'; import { LinkMenu } from './linking/LinkMenu'; import "./MainView.scss"; @@ -184,7 +185,7 @@ export class MainView extends React.Component { fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical, fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll, fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines, - fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt]); + fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt, fa.faSquareRootAlt]); this.initAuthenticationRouters(); } @@ -636,6 +637,7 @@ export class MainView extends React.Component { + {this.snapLines}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e2ea81392..7d60387de 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -50,6 +50,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { InkTranscription } from "../../InkTranscription"; export const panZoomSchema = createSchema({ _panX: "number", @@ -492,7 +493,9 @@ export class CollectionFreeFormView extends CollectionSubView { return { X: pt.X, Y: pt.Y, time: times[i] } }); + const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), strokes, { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() }); this.addDocument(inkDoc); e.stopPropagation(); @@ -1477,6 +1480,11 @@ export class CollectionFreeFormView extends CollectionSubView this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); this.props.ContainingCollectionView && appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" }); + + this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to text", event: () => this.transcribeStrokes(false), icon: "font" }); + + this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" }); + !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); @@ -1536,6 +1544,15 @@ export class CollectionFreeFormView extends CollectionSubView { + if (this.props.Document._isGroup) { + const inkdocs = this.childDocs.filter(s => s.type === DocumentType.INK); + InkTranscription.Instance.transcribeInk(inkdocs, math, { x: NumCast(this.layoutDoc.x) ?? 0, y: NumCast(this.layoutDoc.y) ?? 0, width: NumCast(this.layoutDoc._width) ?? 0, height: NumCast(this.layoutDoc._height) ?? 0 }, this.addDocument); + } + } + @action setupDragLines = (snapToDraggedDoc: boolean = false) => { const activeDocs = this.getActiveDocuments(); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 1f59f9732..8a8b528f6 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -14,7 +14,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu { public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction; public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; - public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public showMarquee: () => void = unimplementedFunction; public hideMarquee: () => void = unimplementedFunction; public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; @@ -64,16 +63,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu { , ]; - if (false && !SelectionManager.Views().some(v => v.props.Document.type !== DocumentType.INK)) { - buttons.push( - Change to Text
} placement="bottom"> - - ); - } return this.getElement(buttons); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b10b0912f..a828cd981 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,14 +1,15 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc"; +import { AclAdmin, AclAugment, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; +import { ImageField } from "../../../../fields/URLField"; import { GetEffectiveAcl } from "../../../../fields/util"; -import { Utils, intersectRect, returnFalse } from "../../../../Utils"; +import { intersectRect, returnFalse, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; @@ -21,18 +22,17 @@ import { ContextMenu } from "../../ContextMenu"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PresBox } from "../../nodes/trails/PresBox"; import { PresMovement } from "../../nodes/trails/PresEnums"; +import { VideoBox } from "../../nodes/VideoBox"; +import { pasteImageBitmap } from "../../nodes/WebBoxRenderer"; import { PreviewCursor } from "../../PreviewCursor"; +import { StyleLayers } from "../../StyleProvider"; import { CollectionDockingView } from "../CollectionDockingView"; import { SubCollectionViewProps } from "../CollectionSubView"; import { CollectionView } from "../CollectionView"; +import { TreeView } from "../TreeView"; import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu"; import "./MarqueeView.scss"; import React = require("react"); -import { StyleLayers } from "../../StyleProvider"; -import { TreeView } from "../TreeView"; -import { VideoBox } from "../../nodes/VideoBox"; -import { ImageField, WebField } from "../../../../fields/URLField"; -import { pasteImageBitmap } from "../../nodes/WebBoxRenderer"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -265,7 +265,7 @@ export class MarqueeView extends React.Component() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(EquationBox, fieldKey); } public static SelectOnLoad: string = ""; + _ref: React.RefObject = React.createRef(); + @observable _inkOpen = false; + @observable _inkEditor: any; + @observable _inkRef: any; + componentDidMount() { if (EquationBox.SelectOnLoad === this.rootDoc[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { this.props.select(false); @@ -39,7 +45,11 @@ export class EquationBox extends ViewBoxBaseComponent() { } }, { fireImmediately: true }); } - plot: any; + + componentWillUnmount() { + this._inkRef.removeEventListener('exported', this.exportInk); + } + @action keyPressed = (e: KeyboardEvent) => { const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace("px", "")); @@ -65,6 +75,7 @@ export class EquationBox extends ViewBoxBaseComponent() { } if (e.key === "Backspace" && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc); } + onChange = (str: string) => { this.dataDoc.text = str; const style = this._ref.current && getComputedStyle(this._ref.current.element.current); @@ -75,6 +86,58 @@ export class EquationBox extends ViewBoxBaseComponent() { this.layoutDoc._height = Math.max(25, _height); } } + + @action + toggleInk = (e: React.PointerEvent) => { + e.stopPropagation(); + + this._inkOpen = !this._inkOpen; + + if (!this._inkEditor) { + this._inkEditor = this._inkRef ? iink.register(this._inkRef, { + recognitionParams: { + type: 'MATH', + protocol: 'WEBSOCKET', + server: { + host: 'cloud.myscript.com', + applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f', + hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1', + }, + iink: { + math: { + mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'] + }, + export: { + jiix: { + strokes: true + } + } + } + } + }) : null; + + this._inkRef.addEventListener('exported', this.exportInk) + } + } + + convertInk = (e: React.MouseEvent) => { + this._inkRef.editor.convert(); + } + + clearInk = (e: React.MouseEvent) => { + this._inkRef.editor.clear(); + this.onChange(""); + } + + exportInk = (e: any) => { + const exports = e.detail.exports; + if (exports && exports['application/x-latex']) { + this.onChange(exports['application/x-latex']); + console.log(JSON.parse(exports['application/vnd.myscript.jiix']).expressions[0].items[0]); + } + } + + render() { TraceMobx(); const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); @@ -82,18 +145,34 @@ export class EquationBox extends ViewBoxBaseComponent() { onPointerDown={e => !e.ctrlKey && e.stopPropagation()} style={{ transform: `scale(${scale})`, - width: `${100 / scale}%`, + width: `calc(${100 / scale}% + 25px)`, height: `${100 / scale}%`, pointerEvents: !this.props.isSelected() ? "none" : undefined, }} - onKeyDown={e => e.stopPropagation()} - > + onKeyDown={e => e.stopPropagation()}> + + +
+ +
+ +
this._inkRef = r)} + id="editor" onPointerDown={(e) => e.stopPropagation()} + touch-action="none" + style={{ display: this._inkOpen && this.props.isContentActive() ? "block" : "none" }}> + + +
+ ); } } \ No newline at end of file diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 31024e805..48271d3d3 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -19,6 +19,7 @@ export enum InkTool { export interface PointData { X: number; Y: number; + time?: number; } export type Segment = Array; @@ -83,7 +84,7 @@ export class InkField extends ObjectField { } [ToScriptString]() { - return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}} `) + "])"; + return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}, time: ${i.time || 0}} `) + "])"; } [ToString]() { return "InkField"; diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 65f2bf80c..cf59cb3c6 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -8,7 +8,8 @@ export namespace GestureUtils { readonly gesture: Gestures, readonly points: PointData[], readonly bounds: Rect, - readonly text?: any + readonly text?: any, + readonly times?: number[] ) { } } diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 068ac2159..bc7727426 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -8,6 +8,7 @@ declare module 'webrtc-adapter'; declare module 'bezier-curve'; declare module 'fit-curve'; declare module 'react-audio-waveform'; +declare module 'iink-js'; declare module 'reveal'; declare module 'react-reveal'; -- cgit v1.2.3-70-g09d2