From 131f3fa7d1f1a42be58de63ee54fecd20e6d9fbf Mon Sep 17 00:00:00 2001 From: ab Date: Thu, 1 Aug 2019 16:29:56 -0400 Subject: key words successfully extracted --- src/client/views/nodes/ImageBox.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src/client/views') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 9a0615d68..c12fae8e9 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -225,6 +225,7 @@ export class ImageBox extends DocComponent(ImageD let modes: ContextMenuProps[] = []; modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); + modes.push({ description: "Recommend", event: this.extractText, icon: "brain" }); ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" }); ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes, icon: "eye" }); @@ -240,6 +241,18 @@ export class ImageBox extends DocComponent(ImageD CognitiveServices.Image.Manager.analyzer(this.extensionDoc, ["faces"], this.url, Service.Face, converter); } + extractText = () => { + let data = StrCast(this.dataDoc.title); + console.log(data); + let converter = (results: any) => { + results.documents.forEach((doc: any) => { + console.log(doc.keyPhrases); + }); + return new Doc(); + }; + CognitiveServices.Text.Manager.analyzer(this.extensionDoc, ["key words", "key word strings"], data, converter); + } + generateMetadata = (threshold: Confidence = Confidence.Excellent) => { let converter = (results: any) => { let tagDoc = new Doc; -- cgit v1.2.3-70-g09d2 From 38317d4a3687e02a727693b569dfc5980db81854 Mon Sep 17 00:00:00 2001 From: ab Date: Mon, 5 Aug 2019 16:15:59 -0400 Subject: recommender --- package.json | 1 + src/client/cognitive_services/CognitiveServices.ts | 3 ++- src/client/util/Recommender.tsx | 22 ++++++++++++++++++++++ src/client/views/nodes/ImageBox.tsx | 8 +++++--- 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/client/util/Recommender.tsx (limited to 'src/client/views') diff --git a/package.json b/package.json index d469f0c49..9012ff1f7 100644 --- a/package.json +++ b/package.json @@ -214,6 +214,7 @@ "typescript-collections": "^1.3.2", "url-loader": "^1.1.2", "uuid": "^3.3.2", + "word2vec": "^1.1.4", "xoauth2": "^1.2.0", "youtube": "^0.1.0" } diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 8aac20084..6afd2571a 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -251,8 +251,9 @@ export namespace CognitiveServices { analyzer: async (target: Doc, keys: string[], data: string, converter: Converter) => { let results = await ExecuteQuery(Service.Text, Manager, data); console.log(results); - converter(results); + let keyterms = converter(results); //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); + target[keys[0]] = keyterms; console.log("analyzed!"); return null; } diff --git a/src/client/util/Recommender.tsx b/src/client/util/Recommender.tsx new file mode 100644 index 000000000..88d0c85f5 --- /dev/null +++ b/src/client/util/Recommender.tsx @@ -0,0 +1,22 @@ +import React = require("react"); +import { observer } from "mobx-react"; +import { observable, runInAction } from "mobx"; +var w2v = require('word2vec'); + +@observer +export default class Recommender extends React.Component { + + /*** + * Converts text to n-dimensional vector using pretrained word2vec model + */ + text_to_vec(text: string) { + + } + + render() { + return ( +
Recommender System!!!
+ ) + } + +} \ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index c12fae8e9..7388b532c 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -245,12 +245,14 @@ export class ImageBox extends DocComponent(ImageD let data = StrCast(this.dataDoc.title); console.log(data); let converter = (results: any) => { + let keyterms = new List(); results.documents.forEach((doc: any) => { - console.log(doc.keyPhrases); + let keyPhrases = doc.keyPhrases; + keyPhrases.map((kp: string) => keyterms.push(kp)); }); - return new Doc(); + return keyterms; }; - CognitiveServices.Text.Manager.analyzer(this.extensionDoc, ["key words", "key word strings"], data, converter); + CognitiveServices.Text.Manager.analyzer(this.extensionDoc, ["key words"], data, converter); } generateMetadata = (threshold: Confidence = Confidence.Excellent) => { -- cgit v1.2.3-70-g09d2 From 06b59a4ec2f1871846696da22928fc7d54ae02d6 Mon Sep 17 00:00:00 2001 From: ab Date: Tue, 6 Aug 2019 16:01:57 -0400 Subject: word2vec is functional --- package.json | 8 +++++- src/client/cognitive_services/CognitiveServices.ts | 18 ++++++++++++- src/client/views/nodes/ImageBox.tsx | 8 ++++-- src/server/Recommender.ts | 30 ++++++++++++++++++---- src/server/index.ts | 19 ++++++++++---- webpack.config.js | 5 +++- 6 files changed, 73 insertions(+), 15 deletions(-) (limited to 'src/client/views') diff --git a/package.json b/package.json index 9012ff1f7..44d5287bd 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,12 @@ "version": "1.0.0", "description": "", "main": "index.js", + "browser": { + "child_process": false + }, + "node": { + "child_process": "empty" + }, "scripts": { "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --inspect -- src/server/index.ts", @@ -218,4 +224,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} +} \ No newline at end of file diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 6afd2571a..863236b60 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -6,6 +6,8 @@ import { RouteStore } from "../../server/RouteStore"; import { Utils } from "../../Utils"; import { InkData } from "../../new_fields/InkField"; import { UndoManager } from "../util/UndoManager"; +import requestPromise = require("request-promise"); +import { List } from "../../new_fields/List"; type APIManager = { converter: BodyConverter, requester: RequestExecutor, analyzer: AnalysisApplier }; type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise; @@ -255,9 +257,23 @@ export namespace CognitiveServices { //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); target[keys[0]] = keyterms; console.log("analyzed!"); + vectorize(keyterms); return null; - } + }, + + }; + function vectorize(keyterms: any) { + console.log("vectorizing..."); + keyterms = ["father", "king"]; + let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true }; + requestPromise.post(args).then((value) => { + value.forEach((wordvec: any) => { + console.log(wordvec.word); + }); + }); + } + } } \ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 7388b532c..161226c0d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,5 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faImage, faFileAudio, faPaintBrush, faAsterisk } from '@fortawesome/free-solid-svg-icons'; +import { faImage, faFileAudio, faPaintBrush, faAsterisk, faBrain } from '@fortawesome/free-solid-svg-icons'; import { action, observable, computed, runInAction } from 'mobx'; import { observer } from "mobx-react"; import Lightbox from 'react-image-lightbox'; @@ -31,12 +31,14 @@ import { faEye } from '@fortawesome/free-regular-svg-icons'; import { ComputedField } from '../../../new_fields/ScriptField'; import { CompileScript } from '../../util/Scripting'; import { thisExpression } from 'babel-types'; +import { Recommender } from '../../../server/Recommender'; +import requestPromise = require('request-promise'); var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); -library.add(faImage, faEye, faPaintBrush); +library.add(faImage, faEye, faPaintBrush, faBrain); library.add(faFileAudio, faAsterisk); @@ -253,6 +255,8 @@ export class ImageBox extends DocComponent(ImageD return keyterms; }; CognitiveServices.Text.Manager.analyzer(this.extensionDoc, ["key words"], data, converter); + // request recommender + //fetch(Utils.prepend("/recommender"), { body: body, method: "POST", headers: { "content-type": "application/json" } }).then((value) => console.log(value)); } generateMetadata = (threshold: Confidence = Confidence.Excellent) => { diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index 6d9ca6486..3c71f3aa1 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -3,25 +3,27 @@ var w2v = require('word2vec'); export class Recommender { private _model: any; + static Instance: Recommender; constructor() { console.log("creating recommender..."); + Recommender.Instance = this; } - public loadModel(): Promise { + private loadModel(): Promise { let self = this; return new Promise(res => { w2v.loadModel("./node_modules/word2vec/vectors.txt", function (err: any, model: any) { - console.log(err); - console.log(model); self._model = model; - console.log(model.similarity('father', 'mother')); res(model); }); }); } - public testModel() { + public async testModel() { + if (!this._model) { + await this.loadModel(); + } if (this._model) { let similarity = this._model.similarity('father', 'mother'); console.log(similarity); @@ -30,4 +32,22 @@ export class Recommender { console.log("model not found :("); } } + + public async testInstance(text: string) { + if (!this._model) { + await this.loadModel(); + } + console.log(text); + } + + public async vectorize(text: string[]) { + if (!this._model) { + await this.loadModel(); + } + if (this._model) { + let word_vecs = this._model.getVectors(text); + console.log(word_vecs[0]); + return word_vecs; + } + } } diff --git a/src/server/index.ts b/src/server/index.ts index dda5a870a..67087fc1f 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -52,11 +52,6 @@ const probe = require("probe-image-size"); var SolrNode = require('solr-node'); var shell = require('shelljs'); -let recommender = new Recommender(); -recommender.loadModel().then(() => { - recommender.testModel(); -}); - const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest)); let youtubeApiKey: string; YoutubeApi.readApiKey((apiKey: string) => youtubeApiKey = apiKey); @@ -651,6 +646,20 @@ app.use(RouteStore.corsProxy, (req, res) => { }).pipe(res); }); +//// + +let recommender = new Recommender(); +recommender.testModel(); + +app.post("/recommender", async (req, res) => { + let keyphrases = req.body.keyphrases; + let wordvecs = await recommender.vectorize(keyphrases); + res.send(wordvecs); +}); + + +///// + app.get(RouteStore.delete, (req, res) => { if (release) { res.send("no"); diff --git a/webpack.config.js b/webpack.config.js index 5e0a6a883..6a14dfcda 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -93,5 +93,8 @@ module.exports = { warnings: true, errors: true } - } + }, + externals: [ + 'child_process' + ] }; \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 17b27d3575d3f91f461262e5ad72a457238d198a Mon Sep 17 00:00:00 2001 From: ab Date: Wed, 7 Aug 2019 16:28:51 -0400 Subject: correlation matrix completed --- package.json | 2 +- src/client/ClientRecommender.ts | 101 +++++++++++++++++++++ src/client/cognitive_services/CognitiveServices.ts | 22 +++-- src/client/views/MainView.tsx | 7 ++ .../collectionFreeForm/CollectionFreeFormView.tsx | 19 +++- src/client/views/nodes/ImageBox.tsx | 14 +-- src/server/Recommender.ts | 27 +++++- 7 files changed, 166 insertions(+), 26 deletions(-) create mode 100644 src/client/ClientRecommender.ts (limited to 'src/client/views') diff --git a/package.json b/package.json index 44d5287bd..ebb0c35a8 100644 --- a/package.json +++ b/package.json @@ -224,4 +224,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/ClientRecommender.ts b/src/client/ClientRecommender.ts new file mode 100644 index 000000000..7ff79ab50 --- /dev/null +++ b/src/client/ClientRecommender.ts @@ -0,0 +1,101 @@ +import { Doc } from "../new_fields/Doc"; +import { StrCast } from "../new_fields/Types"; +import { List } from "../new_fields/List"; +import { CognitiveServices } from "./cognitive_services/CognitiveServices"; + + +var assert = require('assert'); + +export class ClientRecommender { + + static Instance: ClientRecommender; + private docVectors: Set; + + constructor() { + //console.log("creating client recommender..."); + ClientRecommender.Instance = this; + this.docVectors = new Set(); + } + + + /*** + * Computes the cosine similarity between two vectors in Euclidean space. + */ + + private distance(vector1: number[], vector2: number[]) { + assert(vector1.length === vector2.length, "Vectors are not the same length"); + var dotproduct = 0; + var mA = 0; + var mB = 0; + for (let i = 0; i < vector1.length; i++) { // here you missed the i++ + dotproduct += (vector1[i] * vector2[i]); + mA += (vector1[i] * vector1[i]); + mB += (vector2[i] * vector2[i]); + } + mA = Math.sqrt(mA); + mB = Math.sqrt(mB); + var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets + return similarity; + } + + /*** + * Computes the mean of a set of vectors + */ + + public mean(paragraph: Set) { + const n = 200; + const num_words = paragraph.size; + let meanVector = new Array(n).fill(0); // mean vector + paragraph.forEach((wordvec: number[]) => { + for (let i = 0; i < n; i++) { + meanVector[i] += wordvec[i]; + } + }); + meanVector = meanVector.map(x => x / num_words); + this.addToDocSet(meanVector); + return meanVector; + } + + private addToDocSet(vector: number[]) { + if (this.docVectors) { + this.docVectors.add(vector); + } + } + + /*** + * Uses Cognitive Services to extract keywords from a document + */ + + public async extractText(dataDoc: Doc, extDoc: Doc) { + let data = StrCast(dataDoc.title); + //console.log(data); + let converter = (results: any) => { + let keyterms = new List(); + results.documents.forEach((doc: any) => { + let keyPhrases = doc.keyPhrases; + keyPhrases.map((kp: string) => keyterms.push(kp)); + }); + return keyterms; + }; + await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter); + } + + /*** + * Creates distance matrix for all Documents analyzed + */ + + public createDistanceMatrix(documents: Set = this.docVectors) { + const documents_list = Array.from(documents); + const n = documents_list.length; + var matrix = new Array(n).fill(0).map(() => new Array(n).fill(0)); + for (let i = 0; i < n; i++) { + var doc1 = documents_list[i]; + for (let j = 0; j < n; j++) { + var doc2 = documents_list[j]; + matrix[i][j] = this.distance(doc1, doc2); + } + } + return matrix; + } + +} \ No newline at end of file diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 863236b60..cc366abc2 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -8,6 +8,7 @@ import { InkData } from "../../new_fields/InkField"; import { UndoManager } from "../util/UndoManager"; import requestPromise = require("request-promise"); import { List } from "../../new_fields/List"; +import { ClientRecommender } from "../ClientRecommender"; type APIManager = { converter: BodyConverter, requester: RequestExecutor, analyzer: AnalysisApplier }; type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise; @@ -257,20 +258,21 @@ export namespace CognitiveServices { //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); target[keys[0]] = keyterms; console.log("analyzed!"); - vectorize(keyterms); - return null; - }, - - + await vectorize(keyterms); + } }; - function vectorize(keyterms: any) { + async function vectorize(keyterms: any) { console.log("vectorizing..."); - keyterms = ["father", "king"]; + //keyterms = ["father", "king"]; let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true }; - requestPromise.post(args).then((value) => { - value.forEach((wordvec: any) => { - console.log(wordvec.word); + await requestPromise.post(args).then(async (wordvecs) => { + var vectorValues = new Set(); + wordvecs.forEach((wordvec: any) => { + //console.log(wordvec.word); + vectorValues.add(wordvec.values as number[]); }); + ClientRecommender.Instance.mean(vectorValues); + //console.log(vectorValues.size); }); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 2ecf5fd85..97964166a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -39,6 +39,7 @@ import { FilterBox } from './search/FilterBox'; import { CollectionTreeView } from './collections/CollectionTreeView'; import { ClientUtils } from '../util/ClientUtils'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; +//import { DocumentManager } from '../util/DocumentManager'; @observer export class MainView extends React.Component { @@ -435,6 +436,12 @@ export class MainView extends React.Component { ; } + // clusterDocuments = () => { + // DocumentManager.Instance.DocumentViews(); + // } + + + @action diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 29f9b1429..9344b43d2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,6 +1,6 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; -import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; +import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faBrain } from "@fortawesome/free-solid-svg-icons"; import { action, computed } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc"; @@ -37,8 +37,9 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); +import { ClientRecommender } from "../../../ClientRecommender"; -library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload); +library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBrain); export const panZoomSchema = createSchema({ panX: "number", @@ -596,6 +597,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { input.click(); } }); + ContextMenu.Instance.addItem({ + description: "Recommender System", + event: async () => { + new ClientRecommender(); + let activedocs = this.getActiveDocuments(); + await Promise.all(activedocs.map((doc: Doc) => { + console.log(StrCast(doc.title)); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + })); + console.log(ClientRecommender.Instance.createDistanceMatrix()); + }, + icon: "brain" + }); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 161226c0d..660772c0e 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -31,7 +31,7 @@ import { faEye } from '@fortawesome/free-regular-svg-icons'; import { ComputedField } from '../../../new_fields/ScriptField'; import { CompileScript } from '../../util/Scripting'; import { thisExpression } from 'babel-types'; -import { Recommender } from '../../../server/Recommender'; +//import { Recommender } from '../../../server/Recommender'; import requestPromise = require('request-promise'); var requestImageSize = require('../../util/request-image-size'); var path = require('path'); @@ -244,17 +244,7 @@ export class ImageBox extends DocComponent(ImageD } extractText = () => { - let data = StrCast(this.dataDoc.title); - console.log(data); - let converter = (results: any) => { - let keyterms = new List(); - results.documents.forEach((doc: any) => { - let keyPhrases = doc.keyPhrases; - keyPhrases.map((kp: string) => keyterms.push(kp)); - }); - return keyterms; - }; - CognitiveServices.Text.Manager.analyzer(this.extensionDoc, ["key words"], data, converter); + //Recommender.Instance.extractText(this.dataDoc, this.extensionDoc); // request recommender //fetch(Utils.prepend("/recommender"), { body: body, method: "POST", headers: { "content-type": "application/json" } }).then((value) => console.log(value)); } diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index 3c71f3aa1..ea59703c3 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -1,4 +1,10 @@ +//import { Doc } from "../new_fields/Doc"; +//import { StrCast } from "../new_fields/Types"; +//import { List } from "../new_fields/List"; +//import { CognitiveServices } from "../client/cognitive_services/CognitiveServices"; + var w2v = require('word2vec'); +var assert = require('assert'); export class Recommender { @@ -10,6 +16,10 @@ export class Recommender { Recommender.Instance = this; } + /*** + * Loads pre-trained model from word2vec + */ + private loadModel(): Promise { let self = this; return new Promise(res => { @@ -20,6 +30,10 @@ export class Recommender { }); } + /*** + * Testing + */ + public async testModel() { if (!this._model) { await this.loadModel(); @@ -33,6 +47,10 @@ export class Recommender { } } + /*** + * Tests if instance exists + */ + public async testInstance(text: string) { if (!this._model) { await this.loadModel(); @@ -40,14 +58,21 @@ export class Recommender { console.log(text); } + /*** + * Uses model to convert words to vectors + */ + public async vectorize(text: string[]) { if (!this._model) { await this.loadModel(); } if (this._model) { let word_vecs = this._model.getVectors(text); - console.log(word_vecs[0]); return word_vecs; } } + + + + } -- cgit v1.2.3-70-g09d2 From 17f28e393e0c24fcace33a3ecd5564bc766fe685 Mon Sep 17 00:00:00 2001 From: ab Date: Thu, 8 Aug 2019 13:00:51 -0400 Subject: fuck you react --- src/client/ClientRecommender.ts | 101 -------------- src/client/ClientRecommender.tsx | 148 +++++++++++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/server/Recommender.ts | 2 + 4 files changed, 153 insertions(+), 102 deletions(-) delete mode 100644 src/client/ClientRecommender.ts create mode 100644 src/client/ClientRecommender.tsx (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.ts b/src/client/ClientRecommender.ts deleted file mode 100644 index 7ff79ab50..000000000 --- a/src/client/ClientRecommender.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Doc } from "../new_fields/Doc"; -import { StrCast } from "../new_fields/Types"; -import { List } from "../new_fields/List"; -import { CognitiveServices } from "./cognitive_services/CognitiveServices"; - - -var assert = require('assert'); - -export class ClientRecommender { - - static Instance: ClientRecommender; - private docVectors: Set; - - constructor() { - //console.log("creating client recommender..."); - ClientRecommender.Instance = this; - this.docVectors = new Set(); - } - - - /*** - * Computes the cosine similarity between two vectors in Euclidean space. - */ - - private distance(vector1: number[], vector2: number[]) { - assert(vector1.length === vector2.length, "Vectors are not the same length"); - var dotproduct = 0; - var mA = 0; - var mB = 0; - for (let i = 0; i < vector1.length; i++) { // here you missed the i++ - dotproduct += (vector1[i] * vector2[i]); - mA += (vector1[i] * vector1[i]); - mB += (vector2[i] * vector2[i]); - } - mA = Math.sqrt(mA); - mB = Math.sqrt(mB); - var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets - return similarity; - } - - /*** - * Computes the mean of a set of vectors - */ - - public mean(paragraph: Set) { - const n = 200; - const num_words = paragraph.size; - let meanVector = new Array(n).fill(0); // mean vector - paragraph.forEach((wordvec: number[]) => { - for (let i = 0; i < n; i++) { - meanVector[i] += wordvec[i]; - } - }); - meanVector = meanVector.map(x => x / num_words); - this.addToDocSet(meanVector); - return meanVector; - } - - private addToDocSet(vector: number[]) { - if (this.docVectors) { - this.docVectors.add(vector); - } - } - - /*** - * Uses Cognitive Services to extract keywords from a document - */ - - public async extractText(dataDoc: Doc, extDoc: Doc) { - let data = StrCast(dataDoc.title); - //console.log(data); - let converter = (results: any) => { - let keyterms = new List(); - results.documents.forEach((doc: any) => { - let keyPhrases = doc.keyPhrases; - keyPhrases.map((kp: string) => keyterms.push(kp)); - }); - return keyterms; - }; - await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter); - } - - /*** - * Creates distance matrix for all Documents analyzed - */ - - public createDistanceMatrix(documents: Set = this.docVectors) { - const documents_list = Array.from(documents); - const n = documents_list.length; - var matrix = new Array(n).fill(0).map(() => new Array(n).fill(0)); - for (let i = 0; i < n; i++) { - var doc1 = documents_list[i]; - for (let j = 0; j < n; j++) { - var doc2 = documents_list[j]; - matrix[i][j] = this.distance(doc1, doc2); - } - } - return matrix; - } - -} \ No newline at end of file diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx new file mode 100644 index 000000000..2344ee490 --- /dev/null +++ b/src/client/ClientRecommender.tsx @@ -0,0 +1,148 @@ +import { Doc } from "../new_fields/Doc"; +import { StrCast } from "../new_fields/Types"; +import { List } from "../new_fields/List"; +import { CognitiveServices } from "./cognitive_services/CognitiveServices"; +import React = require("react"); +import { observer } from "mobx-react"; +import { observable, action, computed, reaction } from "mobx"; +var assert = require('assert'); +import Table from 'react-bootstrap/Table'; + +export interface RecommenderProps { + title: string; +} + +@observer +export class ClientRecommender extends React.Component { + + static Instance: ClientRecommender; + private docVectors: Set; + private corr_matrix = observable([[0, 0], [0, 0]]); + @observable private firstnum = 0; + //@observable private corr_matrix: number[][] = [[0, 0], [0, 0]]; + + constructor(props: RecommenderProps) { + //console.log("creating client recommender..."); + super(props); + ClientRecommender.Instance = this; + this.docVectors = new Set(); + //this.corr_matrix = [[0, 0], [0, 0]]; + } + + + /*** + * Computes the cosine similarity between two vectors in Euclidean space. + */ + + private distance(vector1: number[], vector2: number[]) { + assert(vector1.length === vector2.length, "Vectors are not the same length"); + var dotproduct = 0; + var mA = 0; + var mB = 0; + for (let i = 0; i < vector1.length; i++) { // here you missed the i++ + dotproduct += (vector1[i] * vector2[i]); + mA += (vector1[i] * vector1[i]); + mB += (vector2[i] * vector2[i]); + } + mA = Math.sqrt(mA); + mB = Math.sqrt(mB); + var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets + return similarity; + } + + /*** + * Computes the mean of a set of vectors + */ + + public mean(paragraph: Set) { + const n = 200; + const num_words = paragraph.size; + let meanVector = new Array(n).fill(0); // mean vector + paragraph.forEach((wordvec: number[]) => { + for (let i = 0; i < n; i++) { + meanVector[i] += wordvec[i]; + } + }); + meanVector = meanVector.map(x => x / num_words); + this.addToDocSet(meanVector); + return meanVector; + } + + private addToDocSet(vector: number[]) { + if (this.docVectors) { + this.docVectors.add(vector); + } + } + + /*** + * Uses Cognitive Services to extract keywords from a document + */ + + public async extractText(dataDoc: Doc, extDoc: Doc) { + let data = StrCast(dataDoc.title); + //console.log(data); + let converter = (results: any) => { + let keyterms = new List(); + results.documents.forEach((doc: any) => { + let keyPhrases = doc.keyPhrases; + keyPhrases.map((kp: string) => keyterms.push(kp)); + }); + return keyterms; + }; + await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter); + } + + /*** + * Creates distance matrix for all Documents analyzed + */ + + @action + public createDistanceMatrix(documents: Set = this.docVectors) { + //this.corr_matrix[0][0] = 500; + this.firstnum = 500; + const documents_list = Array.from(documents); + const n = documents_list.length; + var matrix = new Array(n).fill(0).map(() => new Array(n).fill(0)); + for (let i = 0; i < n; i++) { + var doc1 = documents_list[i]; + for (let j = 0; j < n; j++) { + var doc2 = documents_list[j]; + matrix[i][j] = this.distance(doc1, doc2); + } + } + //this.corr_matrix = matrix; + + return matrix; + } + + @computed get first_num() { + return this.firstnum; + } + + dumb_reaction = reaction( + () => this.first_num, + number => { + console.log("number has changed", number); + this.forceUpdate(); + } + ); + + render() { + return (
+

{this.props.title ? this.props.title : "hello"}

+ + + + + + + + + + + +
{this.first_num}{this.corr_matrix[0][1]}
{this.corr_matrix[1][0]}{this.corr_matrix[1][1]}
+
); + } + +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9344b43d2..5259b9b49 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -523,6 +523,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } + @action onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; layoutItems.push({ @@ -600,7 +601,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ContextMenu.Instance.addItem({ description: "Recommender System", event: async () => { - new ClientRecommender(); + // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); let activedocs = this.getActiveDocuments(); await Promise.all(activedocs.map((doc: Doc) => { console.log(StrCast(doc.title)); @@ -715,6 +716,7 @@ class CollectionFreeFormViewPannableContents extends React.Component otherwise, reactions won't fire return
{this.props.children} +
; } } \ No newline at end of file diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index ea59703c3..d175b67c7 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -10,6 +10,7 @@ export class Recommender { private _model: any; static Instance: Recommender; + private dimension: number = 0; constructor() { console.log("creating recommender..."); @@ -25,6 +26,7 @@ export class Recommender { return new Promise(res => { w2v.loadModel("./node_modules/word2vec/vectors.txt", function (err: any, model: any) { self._model = model; + self.dimension = model.size; res(model); }); }); -- cgit v1.2.3-70-g09d2 From 116f218c2eeec377fb465027bcfaa7521d9af492 Mon Sep 17 00:00:00 2001 From: ab Date: Thu, 8 Aug 2019 17:48:58 -0400 Subject: euclidian --- src/client/ClientRecommender.scss | 8 ++ src/client/ClientRecommender.tsx | 100 +++++++++++++-------- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- 3 files changed, 71 insertions(+), 40 deletions(-) create mode 100644 src/client/ClientRecommender.scss (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.scss b/src/client/ClientRecommender.scss new file mode 100644 index 000000000..710d80f34 --- /dev/null +++ b/src/client/ClientRecommender.scss @@ -0,0 +1,8 @@ +@import "/views/globalCssVariables.scss"; + +.space{ + border: 1px dashed blue; + border-spacing: 25px; + border-collapse: separate; + align-content: center; +} \ No newline at end of file diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 2344ee490..0793f0887 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -6,7 +6,7 @@ import React = require("react"); import { observer } from "mobx-react"; import { observable, action, computed, reaction } from "mobx"; var assert = require('assert'); -import Table from 'react-bootstrap/Table'; +import "./ClientRecommender.scss"; export interface RecommenderProps { title: string; @@ -17,37 +17,53 @@ export class ClientRecommender extends React.Component { static Instance: ClientRecommender; private docVectors: Set; - private corr_matrix = observable([[0, 0], [0, 0]]); - @observable private firstnum = 0; - //@observable private corr_matrix: number[][] = [[0, 0], [0, 0]]; + @observable private corr_matrix = [[0, 0], [0, 0]]; constructor(props: RecommenderProps) { //console.log("creating client recommender..."); super(props); - ClientRecommender.Instance = this; + if (!ClientRecommender.Instance) ClientRecommender.Instance = this; this.docVectors = new Set(); //this.corr_matrix = [[0, 0], [0, 0]]; } + @action + public reset_docs() { + this.docVectors = new Set(); + this.corr_matrix = [[0, 0], [0, 0]]; + } /*** * Computes the cosine similarity between two vectors in Euclidean space. */ - private distance(vector1: number[], vector2: number[]) { + private distance(vector1: number[], vector2: number[], metric: string = "cosine") { assert(vector1.length === vector2.length, "Vectors are not the same length"); - var dotproduct = 0; - var mA = 0; - var mB = 0; - for (let i = 0; i < vector1.length; i++) { // here you missed the i++ - dotproduct += (vector1[i] * vector2[i]); - mA += (vector1[i] * vector1[i]); - mB += (vector2[i] * vector2[i]); + let similarity: number; + switch (metric) { + case "cosine": + var dotproduct = 0; + var mA = 0; + var mB = 0; + for (let i = 0; i < vector1.length; i++) { // here you missed the i++ + dotproduct += (vector1[i] * vector2[i]); + mA += (vector1[i] * vector1[i]); + mB += (vector2[i] * vector2[i]); + } + mA = Math.sqrt(mA); + mB = Math.sqrt(mB); + similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets + return similarity; + case "euclidian": + var sum = 0; + for (let i = 0; i < vector1.length; i++) { + sum += Math.pow(vector1[i] - vector2[i], 2); + } + similarity = Math.sqrt(sum); + return similarity; + default: + return 0; } - mA = Math.sqrt(mA); - mB = Math.sqrt(mB); - var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets - return similarity; } /*** @@ -98,8 +114,6 @@ export class ClientRecommender extends React.Component { @action public createDistanceMatrix(documents: Set = this.docVectors) { - //this.corr_matrix[0][0] = 500; - this.firstnum = 500; const documents_list = Array.from(documents); const n = documents_list.length; var matrix = new Array(n).fill(0).map(() => new Array(n).fill(0)); @@ -107,40 +121,48 @@ export class ClientRecommender extends React.Component { var doc1 = documents_list[i]; for (let j = 0; j < n; j++) { var doc2 = documents_list[j]; - matrix[i][j] = this.distance(doc1, doc2); + matrix[i][j] = this.distance(doc1, doc2, "euclidian"); } } - //this.corr_matrix = matrix; - + this.corr_matrix = matrix; return matrix; } - @computed get first_num() { - return this.firstnum; - } - - dumb_reaction = reaction( - () => this.first_num, - number => { - console.log("number has changed", number); - this.forceUpdate(); + @computed + private get generateRows() { + const n = this.corr_matrix.length; + let rows: React.ReactElement[] = []; + for (let i = 0; i < n; i++) { + let children: React.ReactElement[] = []; + for (let j = 0; j < n; j++) { + let cell = React.createElement("td", this.corr_matrix[i][j]); + children.push(cell); + } + let row = React.createElement("tr", { children: children }); + rows.push(row); } - ); + return rows; + } render() { return (

{this.props.title ? this.props.title : "hello"}

- + {/*
- - - + + + - - - + + + +
{this.first_num}{this.corr_matrix[0][1]}
{this.corr_matrix[0][0].toFixed(4)}{this.corr_matrix[0][1].toFixed(4)}
{this.corr_matrix[1][0]}{this.corr_matrix[1][1]}
{this.corr_matrix[1][0].toFixed(4)}{this.corr_matrix[1][1].toFixed(4)}
*/} + + + {this.generateRows} +
); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5259b9b49..2b9f32136 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -603,6 +603,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { event: async () => { // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); let activedocs = this.getActiveDocuments(); + ClientRecommender.Instance.reset_docs(); await Promise.all(activedocs.map((doc: Doc) => { console.log(StrCast(doc.title)); const extdoc = doc.data_ext as Doc; @@ -716,7 +717,7 @@ class CollectionFreeFormViewPannableContents extends React.Component otherwise, reactions won't fire return
{this.props.children} - +
; } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From b8ab5a823ae22b021c09dfd713b77211a51b3eae Mon Sep 17 00:00:00 2001 From: ab Date: Fri, 9 Aug 2019 17:04:25 -0400 Subject: idk man --- solr-8.1.1/server/solr/dash/conf/schema.xml | 2 +- src/client/ClientRecommender.scss | 4 ++++ src/client/ClientRecommender.tsx | 17 ++++++++++------- src/client/util/SearchUtil.ts | 21 ++++++++++++++++++++- src/client/views/SearchBox.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +++++- src/server/Search.ts | 2 +- 7 files changed, 42 insertions(+), 12 deletions(-) (limited to 'src/client/views') diff --git a/solr-8.1.1/server/solr/dash/conf/schema.xml b/solr-8.1.1/server/solr/dash/conf/schema.xml index 8610786af..c0a4bab07 100644 --- a/solr-8.1.1/server/solr/dash/conf/schema.xml +++ b/solr-8.1.1/server/solr/dash/conf/schema.xml @@ -52,7 +52,7 @@ - + diff --git a/src/client/ClientRecommender.scss b/src/client/ClientRecommender.scss index 710d80f34..49163cdc8 100644 --- a/src/client/ClientRecommender.scss +++ b/src/client/ClientRecommender.scss @@ -5,4 +5,8 @@ border-spacing: 25px; border-collapse: separate; align-content: center; +} + +.wrapper{ + text-align: -webkit-center; } \ No newline at end of file diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 0793f0887..ddaa8a7fc 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -7,6 +7,7 @@ import { observer } from "mobx-react"; import { observable, action, computed, reaction } from "mobx"; var assert = require('assert'); import "./ClientRecommender.scss"; +import { JSXElement } from "babel-types"; export interface RecommenderProps { title: string; @@ -131,22 +132,24 @@ export class ClientRecommender extends React.Component { @computed private get generateRows() { const n = this.corr_matrix.length; - let rows: React.ReactElement[] = []; + let rows: JSX.Element[] = []; for (let i = 0; i < n; i++) { - let children: React.ReactElement[] = []; + let children: JSX.Element[] = []; for (let j = 0; j < n; j++) { - let cell = React.createElement("td", this.corr_matrix[i][j]); + //let cell = React.createElement("td", this.corr_matrix[i][j]); + let cell = {this.corr_matrix[i][j].toFixed(4)}; children.push(cell); } - let row = React.createElement("tr", { children: children }); + //let row = React.createElement("tr", { children: children, key: i }); + let row = {children}; rows.push(row); } return rows; } render() { - return (
-

{this.props.title ? this.props.title : "hello"}

+ return (
+

{this.props.title ? this.props.title : "hello"}

{/* @@ -159,7 +162,7 @@ export class ClientRecommender extends React.Component {
*/} - +
{this.generateRows} diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index ee5a83710..3a3ba1803 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -77,4 +77,23 @@ export namespace SearchUtil { aliasContexts.forEach(result => contexts.aliasContexts.push(...result.ids)); return contexts; } -} \ No newline at end of file + + export async function GetAllDocs() { + const query = "*"; + let response = await rp.get(Utils.prepend('/search'), { + qs: { + query + } + }); + let res: string[] = JSON.parse(response); + const fields = await DocServer.GetRefFields(res); + const docs: Doc[] = []; + for (const id of res) { + const field = fields[id]; + if (field instanceof Doc) { + docs.push(field); + } + } + return docs; + } +} diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx index 33cb63df5..17f99fa05 100644 --- a/src/client/views/SearchBox.tsx +++ b/src/client/views/SearchBox.tsx @@ -47,7 +47,7 @@ export class SearchBox extends React.Component { } @action - getResults = async (query: string) => { + public getResults = async (query: string) => { let response = await rp.get(Utils.prepend('/search'), { qs: { query diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2b9f32136..0beb0086b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -38,6 +38,8 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); import { ClientRecommender } from "../../../ClientRecommender"; +import { SearchUtil } from "../../../util/SearchUtil"; +import { SearchBox } from "../../SearchBox"; library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBrain); @@ -603,9 +605,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { event: async () => { // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); let activedocs = this.getActiveDocuments(); + let allDocs = await SearchUtil.GetAllDocs(); + allDocs.forEach(doc => console.log(doc.title)); ClientRecommender.Instance.reset_docs(); await Promise.all(activedocs.map((doc: Doc) => { - console.log(StrCast(doc.title)); + //console.log(StrCast(doc.title)); const extdoc = doc.data_ext as Doc; return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); })); diff --git a/src/server/Search.ts b/src/server/Search.ts index 723dc101b..44c1b1298 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -14,7 +14,7 @@ export class Search { }); return res; } catch (e) { - // console.warn("Search error: " + e + document); + console.warn("Search error: " + e + document); } } -- cgit v1.2.3-70-g09d2 From 9dd2a31b72e5e527e2dae3b68f856ab8da879e93 Mon Sep 17 00:00:00 2001 From: ab Date: Mon, 12 Aug 2019 16:41:23 -0400 Subject: documentation --- package.json | 2 +- src/client/ClientRecommender.tsx | 18 +++++----- src/client/cognitive_services/CognitiveServices.ts | 42 ++++++++++++---------- src/client/util/SearchUtil.ts | 13 ++++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/server/Recommender.ts | 1 + 6 files changed, 45 insertions(+), 32 deletions(-) (limited to 'src/client/views') diff --git a/package.json b/package.json index 1e2c74411..1c7a10ac8 100644 --- a/package.json +++ b/package.json @@ -225,4 +225,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index ddaa8a7fc..63f85c737 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -75,13 +75,15 @@ export class ClientRecommender extends React.Component { const n = 200; const num_words = paragraph.size; let meanVector = new Array(n).fill(0); // mean vector - paragraph.forEach((wordvec: number[]) => { - for (let i = 0; i < n; i++) { - meanVector[i] += wordvec[i]; - } - }); - meanVector = meanVector.map(x => x / num_words); - this.addToDocSet(meanVector); + if (num_words > 0) { // check to see if paragraph actually was vectorized + paragraph.forEach((wordvec: number[]) => { + for (let i = 0; i < n; i++) { + meanVector[i] += wordvec[i]; + } + }); + meanVector = meanVector.map(x => x / num_words); + this.addToDocSet(meanVector); + } return meanVector; } @@ -106,7 +108,7 @@ export class ClientRecommender extends React.Component { }); return keyterms; }; - await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter); + await CognitiveServices.Text.Appliers.analyzer(extDoc, ["key words"], data, converter); } /*** diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 954a05585..75d0760ed 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -258,32 +258,38 @@ export namespace CognitiveServices { }; console.log("requested!"); return request.post(options); - }, - analyzer: async (target: Doc, keys: string[], data: string, converter: Converter) => { - let results = await ExecuteQuery(Service.Text, Manager, data); + } + }; + + export namespace Appliers { + + export async function vectorize(keyterms: any) { + console.log("vectorizing..."); + //keyterms = ["father", "king"]; + let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true }; + await requestPromise.post(args).then(async (wordvecs) => { + var vectorValues = new Set(); + wordvecs.forEach((wordvec: any) => { + //console.log(wordvec.word); + vectorValues.add(wordvec.values as number[]); + }); + ClientRecommender.Instance.mean(vectorValues); + //console.log(vectorValues.size); + }); + } + + export const analyzer = async (target: Doc, keys: string[], data: string, converter: Converter) => { + let results = await ExecuteQuery(Service.Text, Manager, data); console.log(results); let keyterms = converter(results); //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); target[keys[0]] = keyterms; console.log("analyzed!"); await vectorize(keyterms); - } - }; - async function vectorize(keyterms: any) { - console.log("vectorizing..."); - //keyterms = ["father", "king"]; - let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true }; - await requestPromise.post(args).then(async (wordvecs) => { - var vectorValues = new Set(); - wordvecs.forEach((wordvec: any) => { - //console.log(wordvec.word); - vectorValues.add(wordvec.values as number[]); - }); - ClientRecommender.Instance.mean(vectorValues); - //console.log(vectorValues.size); - }); + }; } } + } \ No newline at end of file diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 3a3ba1803..1fce995d7 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -82,18 +82,21 @@ export namespace SearchUtil { const query = "*"; let response = await rp.get(Utils.prepend('/search'), { qs: { - query + q: query } }); - let res: string[] = JSON.parse(response); - const fields = await DocServer.GetRefFields(res); + let result: IdSearchResult = JSON.parse(response); + const { ids, numFound, highlighting } = result; + const docMap = await DocServer.GetRefFields(ids); const docs: Doc[] = []; - for (const id of res) { - const field = fields[id]; + for (const id of ids) { + const field = docMap[id]; if (field instanceof Doc) { docs.push(field); } } return docs; + // const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc); + // return docs as Doc[]; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e9791df4e..d1e8031fd 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -894,6 +894,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let activedocs = this.getActiveDocuments(); let allDocs = await SearchUtil.GetAllDocs(); allDocs.forEach(doc => console.log(doc.title)); + // clears internal representation of documents as vectors ClientRecommender.Instance.reset_docs(); await Promise.all(activedocs.map((doc: Doc) => { //console.log(StrCast(doc.title)); diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index d175b67c7..1c95d7ea4 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -70,6 +70,7 @@ export class Recommender { } if (this._model) { let word_vecs = this._model.getVectors(text); + return word_vecs; } } -- cgit v1.2.3-70-g09d2 From 843ac47f04f8c9bd687600f9f07fe4c15744e4ec Mon Sep 17 00:00:00 2001 From: Abdullah Ahmed Date: Tue, 13 Aug 2019 11:12:41 -0400 Subject: recommendations --- src/Recommendations.scss | 21 ++++++ src/Recommendations.tsx | 28 ++++++++ src/client/views/nodes/DocumentView.tsx | 113 +++++++++++++++++--------------- 3 files changed, 108 insertions(+), 54 deletions(-) create mode 100644 src/Recommendations.scss create mode 100644 src/Recommendations.tsx (limited to 'src/client/views') diff --git a/src/Recommendations.scss b/src/Recommendations.scss new file mode 100644 index 000000000..5129a59d9 --- /dev/null +++ b/src/Recommendations.scss @@ -0,0 +1,21 @@ +.recommendation-content *{ + display: inline-block; + margin: auto; + border: 1px dashed grey; + padding: 2px 2px; +} + +.recommendation-content { + float: left; + border: 1px solid green; + width: 200px; + align-content: center; +} + +.rec-scroll { + overflow-y: scroll; + height: 300px; + width: auto; + position: absolute; + background: #cdcdcd; +} \ No newline at end of file diff --git a/src/Recommendations.tsx b/src/Recommendations.tsx new file mode 100644 index 000000000..ca1123ef9 --- /dev/null +++ b/src/Recommendations.tsx @@ -0,0 +1,28 @@ +import { observer } from "mobx-react"; +import React = require("react"); +import { Doc } from "./new_fields/Doc"; +import { NumCast } from "./new_fields/Types"; + +export interface RecProps { + documents: { preview: string, similarity: number }[], + node: Doc; +} + +@observer +export class Recommendations extends React.Component { + render() { + const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" + return ( +
+ {this.props.documents.map(doc => { + return ( +
+ +
{doc.similarity}
+
+ ) + })} +
+ ) + } +} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c8eab85c2..24bcc0217 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,6 +40,7 @@ import React = require("react"); import { DictationManager } from '../../util/DictationManager'; import { MainView } from '../MainView'; import requestPromise = require('request-promise'); +import { Recommendations } from '../../../Recommendations'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -680,6 +681,7 @@ export class DocumentView extends DocComponent(Docu } render() { + let documents = [{ preview: "hi", similarity: 0 }]; let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ? this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc); @@ -699,61 +701,64 @@ export class DocumentView extends DocComponent(Docu let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith(" - {!showTitle && !showCaption ? this.contents : -
- -
- {this.contents} -
- {!showTitle ? (null) : -
- StrCast((this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle!])} - SetValue={(value: string) => (Doc.GetProto(this.layoutDoc)[showTitle!] = value) ? true : true} - /> -
- } - {!showCaption ? (null) : -
- +
+
+ {!showTitle && !showCaption ? this.contents : +
+ +
+ {this.contents}
- } -
- } + {!showTitle ? (null) : +
+ StrCast((this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle!])} + SetValue={(value: string) => (Doc.GetProto(this.layoutDoc)[showTitle!] = value) ? true : true} + /> +
+ } + {!showCaption ? (null) : +
+ +
+ } +
+ } +
+
); } -- cgit v1.2.3-70-g09d2 From e03a1b2cc90e0fdb7789f4826e482e9040aa7075 Mon Sep 17 00:00:00 2001 From: ab Date: Tue, 13 Aug 2019 17:26:49 -0400 Subject: 80% done, garbage collection is much needed --- src/Recommendations.scss | 21 --- src/Recommendations.tsx | 28 ---- src/client/util/SearchUtil.ts | 7 +- src/client/views/MainView.tsx | 2 + src/client/views/Recommendations.scss | 65 ++++++++ src/client/views/Recommendations.tsx | 169 +++++++++++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 13 +- src/client/views/nodes/DocumentView.tsx | 34 ++++- src/client/views/nodes/ImageBox.tsx | 21 ++- 9 files changed, 297 insertions(+), 63 deletions(-) delete mode 100644 src/Recommendations.scss delete mode 100644 src/Recommendations.tsx create mode 100644 src/client/views/Recommendations.scss create mode 100644 src/client/views/Recommendations.tsx (limited to 'src/client/views') diff --git a/src/Recommendations.scss b/src/Recommendations.scss deleted file mode 100644 index 5129a59d9..000000000 --- a/src/Recommendations.scss +++ /dev/null @@ -1,21 +0,0 @@ -.recommendation-content *{ - display: inline-block; - margin: auto; - border: 1px dashed grey; - padding: 2px 2px; -} - -.recommendation-content { - float: left; - border: 1px solid green; - width: 200px; - align-content: center; -} - -.rec-scroll { - overflow-y: scroll; - height: 300px; - width: auto; - position: absolute; - background: #cdcdcd; -} \ No newline at end of file diff --git a/src/Recommendations.tsx b/src/Recommendations.tsx deleted file mode 100644 index ca1123ef9..000000000 --- a/src/Recommendations.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { observer } from "mobx-react"; -import React = require("react"); -import { Doc } from "./new_fields/Doc"; -import { NumCast } from "./new_fields/Types"; - -export interface RecProps { - documents: { preview: string, similarity: number }[], - node: Doc; -} - -@observer -export class Recommendations extends React.Component { - render() { - const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" - return ( -
- {this.props.documents.map(doc => { - return ( -
- -
{doc.similarity}
-
- ) - })} -
- ) - } -} \ No newline at end of file diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 1fce995d7..85e593529 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -81,12 +81,13 @@ export namespace SearchUtil { export async function GetAllDocs() { const query = "*"; let response = await rp.get(Utils.prepend('/search'), { - qs: { - q: query - } + qs: + { start: 0, rows: 10000, q: query }, + }); let result: IdSearchResult = JSON.parse(response); const { ids, numFound, highlighting } = result; + console.log(ids.length); const docMap = await DocServer.GetRefFields(ids); const docs: Doc[] = []; for (const id of ids) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 419b15697..0b6fe3876 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -41,6 +41,7 @@ import { ClientUtils } from '../util/ClientUtils'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; //import { DocumentManager } from '../util/DocumentManager'; import { DictationManager } from '../util/DictationManager'; +import { Recommendations } from './Recommendations'; @observer export class MainView extends React.Component { @@ -581,6 +582,7 @@ export class MainView extends React.Component { {this.mainContent} + {this.nodesMenu()} {this.miscButtons} diff --git a/src/client/views/Recommendations.scss b/src/client/views/Recommendations.scss new file mode 100644 index 000000000..5d8f17e37 --- /dev/null +++ b/src/client/views/Recommendations.scss @@ -0,0 +1,65 @@ +@import "globalCssVariables"; + +.rec-content *{ + display: inline-block; + margin: auto; + width: 50; + height: 30px; + border: 1px dashed grey; + padding: 10px 10px; +} + +.rec-content { + float: left; + width: inherit; + align-content: center; +} + +.rec-scroll { + overflow-y: scroll; + overflow-x: hidden; + position: absolute; + // display: flex; + z-index: 10000; + box-shadow: gray 0.2vw 0.2vw 0.4vw; + // flex-direction: column; + background: whitesmoke; + padding-bottom: 10px; + border-radius: 15px; + border: solid #BBBBBBBB 1px; + width: 200px; + text-align: center; + max-height: 250px; + text-transform: uppercase; + color: grey; + letter-spacing: 2px; +} + +.content { + padding: 10px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.image-background { + pointer-events: none; + background-color: transparent; + width: 50%; + text-align: center; + height: 35px; + margin-left: 5px; +} + +img{ + width: 100%; + height: 100%; +} + +.score { + // margin-left: 15px; + width: 50%; + height: 100%; + text-align: center; +} diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx new file mode 100644 index 000000000..8569996b3 --- /dev/null +++ b/src/client/views/Recommendations.tsx @@ -0,0 +1,169 @@ +import { observer } from "mobx-react"; +import React = require("react"); +import { observable, action } from "mobx"; +import Measure from "react-measure"; +import "./Recommendations.scss"; +import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc"; +import { DocumentIcon } from "./nodes/DocumentIcon"; +import { StrCast, NumCast } from "../../new_fields/Types"; +import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils"; +import { Transform } from "../util/Transform"; +import { ObjectField } from "../../new_fields/ObjectField"; +import { DocumentView } from "./nodes/DocumentView"; +import { DocumentType } from "../documents/Documents"; + + +export interface RecProps { + documents: { preview: Doc, similarity: number }[]; + node: Doc; +} + +@observer +export class Recommendations extends React.Component<{}> { + + static Instance: Recommendations; + @observable private _display: boolean = false; + @observable private _pageX: number = 0; + @observable private _pageY: number = 0; + @observable private _width: number = 0; + @observable private _height: number = 0; + @observable private _documents: { preview: Doc, score: number }[] = []; + + constructor(props: {}) { + super(props); + Recommendations.Instance = this; + } + + private DocumentIcon(doc: Doc) { + let layoutresult = StrCast(doc.type); + let renderDoc = doc; + //let box: number[] = []; + if (layoutresult.indexOf(DocumentType.COL) !== -1) { + renderDoc = Doc.MakeDelegate(renderDoc); + let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { + var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; + let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; + return { + x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) + }; + }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); + } + let returnXDimension = () => 50; + let returnYDimension = () => 50; + let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); + let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt + const docview =
+ {/* onPointerDown={action(() => { + this._useIcons = !this._useIcons; + this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); + })} + onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} + onPointerLeave={action(() => this._displayDim = 50)} > */} + +
; + const data = renderDoc.data; + if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); + newRenderDoc.preview = true; + return docview; + + } + + @action + closeMenu = () => { + this._display = false; + } + + @action + resetDocuments = () => { + this._documents = []; + } + + @action + addDocuments = (documents: { preview: Doc, score: number }[]) => { + this._documents = documents; + } + + @action + displayRecommendations(x: number, y: number) { + this._pageX = x; + this._pageY = y; + this._display = true; + } + + static readonly buffer = 20; + + get pageX() { + const x = this._pageX; + if (x < 0) { + return 0; + } + const width = this._width; + if (x + width > window.innerWidth - Recommendations.buffer) { + return window.innerWidth - Recommendations.buffer - width; + } + return x; + } + + get pageY() { + const y = this._pageY; + if (y < 0) { + return 0; + } + const height = this._height; + if (y + height > window.innerHeight - Recommendations.buffer) { + return window.innerHeight - Recommendations.buffer - height; + } + return y; + } + + render() { + if (!this._display) { + return null; + } + let style = { left: this.pageX, top: this.pageY }; + //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" + return ( + { this._width = r.offset.width; this._height = r.offset.height; })}> + {({ measureRef }) => ( +
+

Recommendations

+ {this._documents.map(doc => { + return ( +
+ + {this.DocumentIcon(doc.preview)} + + {doc.score} +
+ ); + })} + +
+ ) + } + +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d1e8031fd..50f7e2dc8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -896,10 +896,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { allDocs.forEach(doc => console.log(doc.title)); // clears internal representation of documents as vectors ClientRecommender.Instance.reset_docs(); - await Promise.all(activedocs.map((doc: Doc) => { - //console.log(StrCast(doc.title)); - const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + await Promise.all(allDocs.map((doc: Doc) => { + console.log(StrCast(doc.title)); + if (doc.type === DocumentType.IMG) { + console.log(doc.title); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + } })); console.log(ClientRecommender.Instance.createDistanceMatrix()); }, @@ -1013,7 +1016,7 @@ class CollectionFreeFormViewPannableContents extends React.Component otherwise, reactions won't fire return
{this.props.children} - + {/* */}
; } } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 24bcc0217..3ce4dbd4f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,9 +40,13 @@ import React = require("react"); import { DictationManager } from '../../util/DictationManager'; import { MainView } from '../MainView'; import requestPromise = require('request-promise'); -import { Recommendations } from '../../../Recommendations'; +import { Recommendations } from '../Recommendations'; +import { SearchUtil } from '../../util/SearchUtil'; +import { ClientRecommender } from '../../ClientRecommender'; +import { DocumentType } from '../../documents/Documents'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? +library.add(fa.faBrain); library.add(fa.faTrash); library.add(fa.faShare); library.add(fa.faDownload); @@ -610,6 +614,33 @@ export class DocumentView extends DocComponent(Docu a.click(); } }); + cm.addItem({ + description: "Recommender System", + event: async () => { + if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); + let documents: Doc[] = []; + let allDocs = await SearchUtil.GetAllDocs(); + allDocs.forEach(doc => console.log(doc.title)); + // clears internal representation of documents as vectors + ClientRecommender.Instance.reset_docs(); + await Promise.all(allDocs.map((doc: Doc) => { + if (doc.type === DocumentType.IMG) { + console.log(StrCast(doc.title)); + documents.push(doc); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + } + })); + console.log(ClientRecommender.Instance.createDistanceMatrix()); + let recDocs: { preview: Doc, score: number }[] = []; + for (let i = 0; i < documents.length; i++) { + recDocs.push({ preview: documents[i], score: i }); + } + Recommendations.Instance.addDocuments(recDocs); + Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY); + }, + icon: "brain" + }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); type User = { email: string, userDocumentId: string }; let usersMenu: ContextMenuProps[] = []; @@ -758,7 +789,6 @@ export class DocumentView extends DocComponent(Docu
} - ); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 73b892e26..45d389ba6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -28,6 +28,9 @@ import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); +import { SearchUtil } from '../../util/SearchUtil'; +import { ClientRecommender } from '../../ClientRecommender'; +import { DocumentType } from '../../documents/Documents'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); @@ -240,10 +243,20 @@ export class ImageBox extends DocComponent(ImageD } } - extractText = () => { - //Recommender.Instance.extractText(this.dataDoc, this.extensionDoc); - // request recommender - //fetch(Utils.prepend("/recommender"), { body: body, method: "POST", headers: { "content-type": "application/json" } }).then((value) => console.log(value)); + extractText = async () => { + //let activedocs = this.getActiveDocuments(); + let allDocs = await SearchUtil.GetAllDocs(); + allDocs.forEach(doc => console.log(doc.title)); + // clears internal representation of documents as vectors + ClientRecommender.Instance.reset_docs(); + await Promise.all(allDocs.map((doc: Doc) => { + //console.log(StrCast(doc.title)); + if (doc.type === DocumentType.IMG) { + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + } + })); + console.log(ClientRecommender.Instance.createDistanceMatrix()); } generateMetadata = (threshold: Confidence = Confidence.Excellent) => { -- cgit v1.2.3-70-g09d2 From e0bfe978e029268b3901b5d098f946b1a6fc7d0d Mon Sep 17 00:00:00 2001 From: ab Date: Thu, 29 Aug 2019 18:43:32 -0400 Subject: ui fixes, datadoc resolved --- src/client/ClientRecommender.tsx | 83 +++++++++++++++------- src/client/cognitive_services/CognitiveServices.ts | 24 ++++--- src/client/views/MainView.tsx | 1 + src/client/views/Recommendations.tsx | 21 ++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 21 ------ src/client/views/nodes/DocumentView.tsx | 26 ++++--- src/client/views/nodes/ImageBox.tsx | 16 ----- 7 files changed, 104 insertions(+), 88 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 63f85c737..a6d1a32b3 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -1,5 +1,5 @@ import { Doc } from "../new_fields/Doc"; -import { StrCast } from "../new_fields/Types"; +import { StrCast, Cast } from "../new_fields/Types"; import { List } from "../new_fields/List"; import { CognitiveServices } from "./cognitive_services/CognitiveServices"; import React = require("react"); @@ -8,30 +8,42 @@ import { observable, action, computed, reaction } from "mobx"; var assert = require('assert'); import "./ClientRecommender.scss"; import { JSXElement } from "babel-types"; +import { ToPlainText, RichTextField } from "../new_fields/RichTextField"; export interface RecommenderProps { title: string; } +export interface RecommenderDocument { + actualDoc: Doc; + vectorDoc: number[]; + score: number; +} + @observer export class ClientRecommender extends React.Component { static Instance: ClientRecommender; - private docVectors: Set; + private mainDoc?: RecommenderDocument; + private docVectors: Set = new Set(); @observable private corr_matrix = [[0, 0], [0, 0]]; constructor(props: RecommenderProps) { //console.log("creating client recommender..."); super(props); if (!ClientRecommender.Instance) ClientRecommender.Instance = this; - this.docVectors = new Set(); - //this.corr_matrix = [[0, 0], [0, 0]]; + ClientRecommender.Instance.docVectors = new Set(); + //ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]]; } @action public reset_docs() { - this.docVectors = new Set(); - this.corr_matrix = [[0, 0], [0, 0]]; + ClientRecommender.Instance.docVectors = new Set(); + ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]]; + } + + public deleteDocs() { + console.log("deleting previews..."); } /*** @@ -67,11 +79,24 @@ export class ClientRecommender extends React.Component { } } + public computeSimilarities() { + ClientRecommender.Instance.docVectors.forEach((doc: RecommenderDocument) => { + if (ClientRecommender.Instance.mainDoc) { + const distance = ClientRecommender.Instance.distance(ClientRecommender.Instance.mainDoc.vectorDoc, doc.vectorDoc, "euclidian"); + doc.score = distance; + } + } + ); + let doclist = Array.from(ClientRecommender.Instance.docVectors); + doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score); + return doclist; + } + /*** * Computes the mean of a set of vectors */ - public mean(paragraph: Set) { + public mean(paragraph: Set, dataDoc: Doc, mainDoc: boolean) { const n = 200; const num_words = paragraph.size; let meanVector = new Array(n).fill(0); // mean vector @@ -82,14 +107,16 @@ export class ClientRecommender extends React.Component { } }); meanVector = meanVector.map(x => x / num_words); - this.addToDocSet(meanVector); + const internalDoc: RecommenderDocument = { actualDoc: dataDoc, vectorDoc: meanVector, score: 0 }; + if (mainDoc) ClientRecommender.Instance.mainDoc = internalDoc; + ClientRecommender.Instance.addToDocSet(internalDoc); } return meanVector; } - private addToDocSet(vector: number[]) { - if (this.docVectors) { - this.docVectors.add(vector); + private addToDocSet(internalDoc: RecommenderDocument) { + if (ClientRecommender.Instance.docVectors) { + ClientRecommender.Instance.docVectors.add(internalDoc); } } @@ -97,9 +124,11 @@ export class ClientRecommender extends React.Component { * Uses Cognitive Services to extract keywords from a document */ - public async extractText(dataDoc: Doc, extDoc: Doc) { - let data = StrCast(dataDoc.title); - //console.log(data); + public async extractText(dataDoc: Doc, extDoc: Doc, mainDoc: boolean = false) { + let fielddata = Cast(dataDoc.data, RichTextField); + let data: string; + fielddata ? data = fielddata[ToPlainText]() : data = ""; + console.log(data); let converter = (results: any) => { let keyterms = new List(); results.documents.forEach((doc: any) => { @@ -108,7 +137,7 @@ export class ClientRecommender extends React.Component { }); return keyterms; }; - await CognitiveServices.Text.Appliers.analyzer(extDoc, ["key words"], data, converter); + await CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc); } /*** @@ -116,7 +145,7 @@ export class ClientRecommender extends React.Component { */ @action - public createDistanceMatrix(documents: Set = this.docVectors) { + public createDistanceMatrix(documents: Set = ClientRecommender.Instance.docVectors) { const documents_list = Array.from(documents); const n = documents_list.length; var matrix = new Array(n).fill(0).map(() => new Array(n).fill(0)); @@ -124,22 +153,22 @@ export class ClientRecommender extends React.Component { var doc1 = documents_list[i]; for (let j = 0; j < n; j++) { var doc2 = documents_list[j]; - matrix[i][j] = this.distance(doc1, doc2, "euclidian"); + matrix[i][j] = ClientRecommender.Instance.distance(doc1.vectorDoc, doc2.vectorDoc, "euclidian"); } } - this.corr_matrix = matrix; + ClientRecommender.Instance.corr_matrix = matrix; return matrix; } @computed private get generateRows() { - const n = this.corr_matrix.length; + const n = ClientRecommender.Instance.corr_matrix.length; let rows: JSX.Element[] = []; for (let i = 0; i < n; i++) { let children: JSX.Element[] = []; for (let j = 0; j < n; j++) { - //let cell = React.createElement("td", this.corr_matrix[i][j]); - let cell =
; + //let cell = React.createElement("td", ClientRecommender.Instance.corr_matrix[i][j]); + let cell = ; children.push(cell); } //let row = React.createElement("tr", { children: children, key: i }); @@ -151,22 +180,22 @@ export class ClientRecommender extends React.Component { render() { return (
-

{this.props.title ? this.props.title : "hello"}

+

{ClientRecommender.Instance.props.title ? ClientRecommender.Instance.props.title : "hello"}

{/*
{this.corr_matrix[i][j].toFixed(4)}{ClientRecommender.Instance.corr_matrix[i][j].toFixed(4)}
- - + + - - + +
{this.corr_matrix[0][0].toFixed(4)}{this.corr_matrix[0][1].toFixed(4)}{ClientRecommender.Instance.corr_matrix[0][0].toFixed(4)}{ClientRecommender.Instance.corr_matrix[0][1].toFixed(4)}
{this.corr_matrix[1][0].toFixed(4)}{this.corr_matrix[1][1].toFixed(4)}{ClientRecommender.Instance.corr_matrix[1][0].toFixed(4)}{ClientRecommender.Instance.corr_matrix[1][1].toFixed(4)}
*/} - {this.generateRows} + {ClientRecommender.Instance.generateRows}
); diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 75d0760ed..874ee433d 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -263,29 +263,35 @@ export namespace CognitiveServices { export namespace Appliers { - export async function vectorize(keyterms: any) { + export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) { console.log("vectorizing..."); //keyterms = ["father", "king"]; let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true }; await requestPromise.post(args).then(async (wordvecs) => { - var vectorValues = new Set(); - wordvecs.forEach((wordvec: any) => { - //console.log(wordvec.word); - vectorValues.add(wordvec.values as number[]); - }); - ClientRecommender.Instance.mean(vectorValues); + if (wordvecs.length > 0) { + console.log("successful vectorization!"); + var vectorValues = new Set(); + wordvecs.forEach((wordvec: any) => { + //console.log(wordvec.word); + vectorValues.add(wordvec.values as number[]); + }); + ClientRecommender.Instance.mean(vectorValues, dataDoc, mainDoc); + } // adds document to internal doc set + else { + console.log("unsuccessful :( word(s) not in vocabulary"); + } //console.log(vectorValues.size); }); } - export const analyzer = async (target: Doc, keys: string[], data: string, converter: Converter) => { + export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: Converter, mainDoc: boolean = false) => { let results = await ExecuteQuery(Service.Text, Manager, data); console.log(results); let keyterms = converter(results); //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); target[keys[0]] = keyterms; console.log("analyzed!"); - await vectorize(keyterms); + await vectorize(keyterms, dataDoc, mainDoc); }; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 57eb30439..3a5795077 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -204,6 +204,7 @@ export class MainView extends React.Component { const targets = document.elementsFromPoint(e.x, e.y); if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) { ContextMenu.Instance.closeMenu(); + Recommendations.Instance.closeMenu(); } }); diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx index 8569996b3..cf1974c69 100644 --- a/src/client/views/Recommendations.tsx +++ b/src/client/views/Recommendations.tsx @@ -10,8 +10,10 @@ import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../ import { Transform } from "../util/Transform"; import { ObjectField } from "../../new_fields/ObjectField"; import { DocumentView } from "./nodes/DocumentView"; -import { DocumentType } from "../documents/Documents"; - +import { DocumentType } from '../documents/DocumentTypes'; +import { ClientRecommender } from "../ClientRecommender"; +import { DocServer } from "../DocServer"; +import { Id } from "../../new_fields/FieldSymbols"; export interface RecProps { documents: { preview: Doc, similarity: number }[]; @@ -28,6 +30,7 @@ export class Recommendations extends React.Component<{}> { @observable private _width: number = 0; @observable private _height: number = 0; @observable private _documents: { preview: Doc, score: number }[] = []; + private previewDocs: Doc[] = []; constructor(props: {}) { super(props); @@ -52,7 +55,8 @@ export class Recommendations extends React.Component<{}> { let returnXDimension = () => 50; let returnYDimension = () => 50; let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); - let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt + //let scale = () => 1; + //let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt const docview =
{/* onPointerDown={action(() => { this._useIcons = !this._useIcons; @@ -62,7 +66,7 @@ export class Recommendations extends React.Component<{}> { onPointerLeave={action(() => this._displayDim = 50)} > */} { ContentScaling={scale} />
; - const data = renderDoc.data; - if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); - newRenderDoc.preview = true; + // const data = renderDoc.data; + // if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); + // newRenderDoc.preview = true; + // this.previewDocs.push(newRenderDoc); return docview; } @@ -92,6 +97,8 @@ export class Recommendations extends React.Component<{}> { @action closeMenu = () => { this._display = false; + this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id])); + this.previewDocs = []; } @action diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2d4775070..3cef93383 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -857,27 +857,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { input.click(); } }); - ContextMenu.Instance.addItem({ - description: "Recommender System", - event: async () => { - // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); - let activedocs = this.getActiveDocuments(); - let allDocs = await SearchUtil.GetAllDocs(); - allDocs.forEach(doc => console.log(doc.title)); - // clears internal representation of documents as vectors - ClientRecommender.Instance.reset_docs(); - await Promise.all(allDocs.map((doc: Doc) => { - console.log(StrCast(doc.title)); - if (doc.type === DocumentType.IMG) { - console.log(doc.title); - const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); - } - })); - console.log(ClientRecommender.Instance.createDistanceMatrix()); - }, - icon: "brain" - }); layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); layoutItems.push({ diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2a6e91272..f708a7a3a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -648,21 +648,31 @@ export class DocumentView extends DocComponent(Docu if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); let documents: Doc[] = []; let allDocs = await SearchUtil.GetAllDocs(); - allDocs.forEach(doc => console.log(doc.title)); + //allDocs.forEach(doc => console.log(doc.title)); // clears internal representation of documents as vectors ClientRecommender.Instance.reset_docs(); await Promise.all(allDocs.map((doc: Doc) => { - if (doc.type === DocumentType.IMG) { - console.log(StrCast(doc.title)); - documents.push(doc); - const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + let mainDoc: boolean = false; + const dataDoc = Doc.GetDataDoc(doc); + if (doc.type === DocumentType.TEXT) { + if (dataDoc === Doc.GetDataDoc(this.props.Document)) { + mainDoc = true; + console.log(StrCast(doc.title)); + } + if (!documents.includes(dataDoc)) { + documents.push(dataDoc); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, mainDoc); + } } })); console.log(ClientRecommender.Instance.createDistanceMatrix()); + const doclist = ClientRecommender.Instance.computeSimilarities(); let recDocs: { preview: Doc, score: number }[] = []; - for (let i = 0; i < documents.length; i++) { - recDocs.push({ preview: documents[i], score: i }); + // tslint:disable-next-line: prefer-for-of + for (let i = 0; i < doclist.length; i++) { + console.log(doclist[i].score); + recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score }); } Recommendations.Instance.addDocuments(recDocs); Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index ec35465eb..d94e92847 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -240,22 +240,6 @@ export class ImageBox extends DocComponent(ImageD } } - extractText = async () => { - //let activedocs = this.getActiveDocuments(); - let allDocs = await SearchUtil.GetAllDocs(); - allDocs.forEach(doc => console.log(doc.title)); - // clears internal representation of documents as vectors - ClientRecommender.Instance.reset_docs(); - await Promise.all(allDocs.map((doc: Doc) => { - //console.log(StrCast(doc.title)); - if (doc.type === DocumentType.IMG) { - const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); - } - })); - console.log(ClientRecommender.Instance.createDistanceMatrix()); - } - generateMetadata = (threshold: Confidence = Confidence.Excellent) => { let converter = (results: any) => { let tagDoc = new Doc; -- cgit v1.2.3-70-g09d2 From 6f6e5c763d9a398e897df94b89fcc7d3845c318e Mon Sep 17 00:00:00 2001 From: ab Date: Fri, 30 Aug 2019 17:57:39 -0400 Subject: minor fixes --- src/client/ClientRecommender.tsx | 6 +++++- src/client/views/Recommendations.scss | 3 ++- src/client/views/Recommendations.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index a6d1a32b3..9953700cc 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -39,6 +39,7 @@ export class ClientRecommender extends React.Component { @action public reset_docs() { ClientRecommender.Instance.docVectors = new Set(); + ClientRecommender.Instance.mainDoc = undefined; ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]]; } @@ -133,7 +134,10 @@ export class ClientRecommender extends React.Component { let keyterms = new List(); results.documents.forEach((doc: any) => { let keyPhrases = doc.keyPhrases; - keyPhrases.map((kp: string) => keyterms.push(kp)); + keyPhrases.map((kp: string) => { + const words = kp.split(" "); + words.forEach((word) => keyterms.push(word)); + }); }); return keyterms; }; diff --git a/src/client/views/Recommendations.scss b/src/client/views/Recommendations.scss index 5d8f17e37..6619d8df3 100644 --- a/src/client/views/Recommendations.scss +++ b/src/client/views/Recommendations.scss @@ -27,7 +27,7 @@ padding-bottom: 10px; border-radius: 15px; border: solid #BBBBBBBB 1px; - width: 200px; + width: 250px; text-align: center; max-height: 250px; text-transform: uppercase; @@ -62,4 +62,5 @@ img{ width: 50%; height: 100%; text-align: center; + margin-left: 10px; } diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx index cf1974c69..63aa12c29 100644 --- a/src/client/views/Recommendations.tsx +++ b/src/client/views/Recommendations.tsx @@ -161,7 +161,7 @@ export class Recommendations extends React.Component<{}> { {this.DocumentIcon(doc.preview)} - {doc.score} + {doc.score.toFixed(4)}
); })} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f708a7a3a..d51c90b61 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -648,7 +648,7 @@ export class DocumentView extends DocComponent(Docu if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); let documents: Doc[] = []; let allDocs = await SearchUtil.GetAllDocs(); - //allDocs.forEach(doc => console.log(doc.title)); + // allDocs.forEach(doc => console.log(doc.title)); // clears internal representation of documents as vectors ClientRecommender.Instance.reset_docs(); await Promise.all(allDocs.map((doc: Doc) => { -- cgit v1.2.3-70-g09d2 From a1c2afe27c75354d4365a79ea202eca94516069e Mon Sep 17 00:00:00 2001 From: ab Date: Wed, 4 Sep 2019 09:52:39 -0400 Subject: stopwords, frequency, proto arxiv --- package.json | 2 + src/client/ClientRecommender.tsx | 75 ++++++++++++++++++++-- src/client/cognitive_services/CognitiveServices.ts | 13 ++-- src/client/util/TooltipTextMenu.scss | 2 +- src/client/views/nodes/DocumentView.tsx | 1 + src/server/Recommender.ts | 29 +++++++++ src/server/index.ts | 1 + 7 files changed, 113 insertions(+), 10 deletions(-) (limited to 'src/client/views') diff --git a/package.json b/package.json index ec5af93b1..d4b5bdab6 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "@types/youtube": "0.0.38", "adm-zip": "^0.4.13", "archiver": "^3.0.3", + "arxiv-api-node": "0.0.2", "async": "^2.6.2", "babel-runtime": "^6.26.0", "bcrypt-nodejs": "0.0.3", @@ -218,6 +219,7 @@ "socket.io-client": "^2.2.0", "solr-node": "^1.2.1", "standard-http-error": "^2.0.1", + "stopword": "^0.3.3", "typescript-collections": "^1.3.2", "url-loader": "^1.1.2", "uuid": "^3.3.2", diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 9953700cc..66f0ae745 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -6,6 +6,7 @@ import React = require("react"); import { observer } from "mobx-react"; import { observable, action, computed, reaction } from "mobx"; var assert = require('assert'); +var sw = require('stopword'); import "./ClientRecommender.scss"; import { JSXElement } from "babel-types"; import { ToPlainText, RichTextField } from "../new_fields/RichTextField"; @@ -130,20 +131,86 @@ export class ClientRecommender extends React.Component { let data: string; fielddata ? data = fielddata[ToPlainText]() : data = ""; console.log(data); - let converter = (results: any) => { + let converter = (results: any, data: string) => { let keyterms = new List(); + let keyterms_counted = new List(); results.documents.forEach((doc: any) => { let keyPhrases = doc.keyPhrases; keyPhrases.map((kp: string) => { - const words = kp.split(" "); - words.forEach((word) => keyterms.push(word)); + const frequency = this.countFrequencies(kp, data); + let words = kp.split(" "); // separates phrase into words + words = this.removeStopWords(words); + words.forEach((word) => { + keyterms.push(word); + for (let i = 0; i < frequency; i++) { + keyterms_counted.push(word); + } + }); + }); + }); + return { keyterms: keyterms, keyterms_counted: keyterms_counted }; + }; + let test = (results: any, data: string) => { + results.documents.forEach((doc: any) => { + let kps = doc.keyPhrases; + kps.map((kp: string) => { + this.countFrequencies(kp, data); }); }); - return keyterms; }; await CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc); } + private countFrequencies(keyphrase: string, paragraph: string) { + let data = paragraph.split(" "); + let kp_array = keyphrase.split(" "); + let num_keywords = kp_array.length; + let par_length = data.length; + let frequency = 0; + // console.log("Paragraph: ", data); + // console.log("Keyphrases:", kp_array); + for (let i = 0; i <= par_length - num_keywords; i++) { + const window = data.slice(i, i + num_keywords); + if (JSON.stringify(window) === JSON.stringify(kp_array)) { + frequency++; + } + } + return frequency; + } + + private removeStopWords(word_array: string[]) { + //console.log(sw.removeStopwords(word_array)); + return sw.removeStopwords(word_array); + } + + /** + * Request to the arXiv server for ML articles. + */ + + arxivrequest = async (query: string) => { + let xhttp = new XMLHttpRequest(); + let serveraddress = "http://export.arxiv.org/api" + let endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=5"; + let promisified = (resolve: any, reject: any) => { + xhttp.onreadystatechange = function () { + if (this.readyState === 4) { + let result = xhttp.response; + switch (this.status) { + case 200: + console.log(result); + return resolve(result); + case 400: + default: + return reject(result); + } + } + }; + xhttp.open("GET", endpoint, true); + xhttp.send(); + }; + return new Promise(promisified); + } + /*** * Creates distance matrix for all Documents analyzed */ diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 874ee433d..eb1dd5197 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -15,6 +15,7 @@ type RequestExecutor = (apiKey: string, body: string, service: Service) => Promi type AnalysisApplier = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any; type BodyConverter = (data: D) => string; type Converter = (results: any) => Field; +type TextConverter = (results: any, data: string) => { keyterms: Field, keyterms_counted: Field }; export type Tag = { name: string, confidence: number }; export type Rectangle = { top: number, left: number, width: number, height: number }; @@ -263,7 +264,7 @@ export namespace CognitiveServices { export namespace Appliers { - export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) { + export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false, data: string) { console.log("vectorizing..."); //keyterms = ["father", "king"]; let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true }; @@ -284,15 +285,17 @@ export namespace CognitiveServices { }); } - export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: Converter, mainDoc: boolean = false) => { + export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, mainDoc: boolean = false) => { let results = await ExecuteQuery(Service.Text, Manager, data); console.log(results); - let keyterms = converter(results); + let keyterms = converter(results, data); //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); - target[keys[0]] = keyterms; + target[keys[0]] = keyterms.keyterms; console.log("analyzed!"); - await vectorize(keyterms, dataDoc, mainDoc); + await vectorize(keyterms.keyterms_counted, dataDoc, mainDoc, data); }; + + // export async function countFrequencies() } } diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index ebf833dbe..ab6cee763 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -351,5 +351,5 @@ .dragger{ color: #eee; - margin-left: 5px; + margin: 5px; } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d51c90b61..a1e64f1c5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -651,6 +651,7 @@ export class DocumentView extends DocComponent(Docu // allDocs.forEach(doc => console.log(doc.title)); // clears internal representation of documents as vectors ClientRecommender.Instance.reset_docs(); + ClientRecommender.Instance.arxivrequest("electrons"); await Promise.all(allDocs.map((doc: Doc) => { let mainDoc: boolean = false; const dataDoc = Doc.GetDataDoc(doc); diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index 781974208..efb5fbbbf 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -5,6 +5,9 @@ var w2v = require('word2vec'); var assert = require('assert'); +var arxivapi = require('arxiv-api-node'); +import requestPromise = require("request-promise"); + export class Recommender { @@ -75,6 +78,32 @@ export class Recommender { } } + public async arxivRequest(query: string) { + // let xhttp = new XMLHttpRequest(); + // let serveraddress = "http://export.arxiv.org/api/query?search_query=all:electron&start=0&max_results=1"; + // let promisified = (resolve: any, reject: any) => { + // xhttp.onreadystatechange = function () { + // if (this.readyState === 4) { + // let result = xhttp.response; + // switch (this.status) { + // case 200: + // console.log(result); + // return resolve(result); + // case 400: + // default: + // return reject(result); + // } + // } + // }; + // xhttp.open("GET", serveraddress, true); + // xhttp.send(); + // }; + // return new Promise(promisified); + + let res = await arxivapi.query("all:electrons"); + console.log(res); + } + diff --git a/src/server/index.ts b/src/server/index.ts index e1ecc4ac0..24ffc466f 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -688,6 +688,7 @@ app.use(RouteStore.corsProxy, (req, res) => { let recommender = new Recommender(); recommender.testModel(); +recommender.arxivRequest("Triangle-GAN"); app.post("/recommender", async (req, res) => { let keyphrases = req.body.keyphrases; -- cgit v1.2.3-70-g09d2 From 435145b366e877c9c10a91408f6fdfc5b612e7f4 Mon Sep 17 00:00:00 2001 From: ab Date: Sat, 7 Sep 2019 17:38:52 -0400 Subject: parser for arxiv api response --- package.json | 1 + src/client/ClientRecommender.tsx | 57 ++++++++++++++++++++++------ src/client/views/Recommendations.tsx | 6 ++- src/client/views/nodes/DocumentView.tsx | 67 ++++++++++++++++----------------- 4 files changed, 84 insertions(+), 47 deletions(-) (limited to 'src/client/views') diff --git a/package.json b/package.json index d4b5bdab6..3486f752b 100644 --- a/package.json +++ b/package.json @@ -140,6 +140,7 @@ "express-session": "^1.15.6", "express-validator": "^5.3.1", "expressjs": "^1.0.1", + "feedparser": "^2.2.9", "flexlayout-react": "^0.3.3", "font-awesome": "^4.7.0", "formidable": "^1.2.1", diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 66f0ae745..0e344dae9 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -7,6 +7,7 @@ import { observer } from "mobx-react"; import { observable, action, computed, reaction } from "mobx"; var assert = require('assert'); var sw = require('stopword'); +var FeedParser = require('feedparser'); import "./ClientRecommender.scss"; import { JSXElement } from "babel-types"; import { ToPlainText, RichTextField } from "../new_fields/RichTextField"; @@ -130,14 +131,23 @@ export class ClientRecommender extends React.Component { let fielddata = Cast(dataDoc.data, RichTextField); let data: string; fielddata ? data = fielddata[ToPlainText]() : data = ""; - console.log(data); let converter = (results: any, data: string) => { let keyterms = new List(); let keyterms_counted = new List(); + let highKP: string[] = [""]; + let high = 0; results.documents.forEach((doc: any) => { let keyPhrases = doc.keyPhrases; keyPhrases.map((kp: string) => { const frequency = this.countFrequencies(kp, data); + if (frequency > high) { + high = frequency; + highKP = [kp]; + + } + else if (frequency === high) { + highKP.push(kp); + } let words = kp.split(" "); // separates phrase into words words = this.removeStopWords(words); words.forEach((word) => { @@ -148,19 +158,18 @@ export class ClientRecommender extends React.Component { }); }); }); + console.log(highKP); + this.sendRequest(highKP); return { keyterms: keyterms, keyterms_counted: keyterms_counted }; }; - let test = (results: any, data: string) => { - results.documents.forEach((doc: any) => { - let kps = doc.keyPhrases; - kps.map((kp: string) => { - this.countFrequencies(kp, data); - }); - }); - }; await CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc); } + private findImportantKPs(keyterms_counted: string[], paragraph: string) { + let imporantSet = new Set(); + + } + private countFrequencies(keyphrase: string, paragraph: string) { let data = paragraph.split(" "); let kp_array = keyphrase.split(" "); @@ -183,6 +192,12 @@ export class ClientRecommender extends React.Component { return sw.removeStopwords(word_array); } + private async sendRequest(keywords: string[]) { + let query = ""; + keywords.forEach((kp: string) => query += kp); + await this.arxivrequest(query); + } + /** * Request to the arXiv server for ML articles. */ @@ -190,14 +205,28 @@ export class ClientRecommender extends React.Component { arxivrequest = async (query: string) => { let xhttp = new XMLHttpRequest(); let serveraddress = "http://export.arxiv.org/api" - let endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=5"; + let endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=1"; let promisified = (resolve: any, reject: any) => { xhttp.onreadystatechange = function () { if (this.readyState === 4) { let result = xhttp.response; + let xml = xhttp.responseXML; + console.log(xml); switch (this.status) { case 200: - console.log(result); + //console.log(result); + if (xml) { + let titles = xml.getElementsByTagName("title"); + if (titles && titles.length > 1) { + let text = titles[1].childNodes[0].nodeValue; + console.log(text); + } + let ids = xml.getElementsByTagName("id"); + if (ids && ids.length > 1) { + let text = ids[1].childNodes[0].nodeValue; + console.log(text); + } + } return resolve(result); case 400: default: @@ -211,6 +240,12 @@ export class ClientRecommender extends React.Component { return new Promise(promisified); } + processArxivResult = (result: any) => { + var xmlDoc = result as XMLDocument; + let text = xmlDoc.getElementsByTagName("title")[0].childNodes[0].nodeValue; + console.log(text); + } + /*** * Creates distance matrix for all Documents analyzed */ diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx index 63aa12c29..d0105ee18 100644 --- a/src/client/views/Recommendations.tsx +++ b/src/client/views/Recommendations.tsx @@ -56,7 +56,9 @@ export class Recommendations extends React.Component<{}> { let returnYDimension = () => 50; let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); //let scale = () => 1; - //let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt + let newRenderDoc = Doc.MakeAlias(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt + newRenderDoc.height = 50; + newRenderDoc.autoHeight = false; const docview =
{/* onPointerDown={action(() => { this._useIcons = !this._useIcons; @@ -66,7 +68,7 @@ export class Recommendations extends React.Component<{}> { onPointerLeave={action(() => this._displayDim = 50)} > */} (Docu }); cm.addItem({ description: "Recommender System", - event: async () => { - if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); - let documents: Doc[] = []; - let allDocs = await SearchUtil.GetAllDocs(); - // allDocs.forEach(doc => console.log(doc.title)); - // clears internal representation of documents as vectors - ClientRecommender.Instance.reset_docs(); - ClientRecommender.Instance.arxivrequest("electrons"); - await Promise.all(allDocs.map((doc: Doc) => { - let mainDoc: boolean = false; - const dataDoc = Doc.GetDataDoc(doc); - if (doc.type === DocumentType.TEXT) { - if (dataDoc === Doc.GetDataDoc(this.props.Document)) { - mainDoc = true; - console.log(StrCast(doc.title)); - } - if (!documents.includes(dataDoc)) { - documents.push(dataDoc); - const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, mainDoc); - } - } - })); - console.log(ClientRecommender.Instance.createDistanceMatrix()); - const doclist = ClientRecommender.Instance.computeSimilarities(); - let recDocs: { preview: Doc, score: number }[] = []; - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < doclist.length; i++) { - console.log(doclist[i].score); - recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score }); - } - Recommendations.Instance.addDocuments(recDocs); - Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY); - }, + event: () => this.recommender(e), icon: "brain" }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); @@ -744,6 +711,38 @@ export class DocumentView extends DocComponent(Docu }); } + recommender = async (e: React.MouseEvent) => { + if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); + let documents: Doc[] = []; + let allDocs = await SearchUtil.GetAllDocs(); + // allDocs.forEach(doc => console.log(doc.title)); + // clears internal representation of documents as vectors + ClientRecommender.Instance.reset_docs(); + //ClientRecommender.Instance.arxivrequest("electrons"); + await Promise.all(allDocs.map((doc: Doc) => { + let mainDoc: boolean = false; + const dataDoc = Doc.GetDataDoc(doc); + if (doc.type === DocumentType.TEXT) { + if (dataDoc === Doc.GetDataDoc(this.props.Document)) { + mainDoc = true; + } + if (!documents.includes(dataDoc)) { + documents.push(dataDoc); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, mainDoc); + } + } + })); + const doclist = ClientRecommender.Instance.computeSimilarities(); + let recDocs: { preview: Doc, score: number }[] = []; + // tslint:disable-next-line: prefer-for-of + for (let i = 0; i < doclist.length; i++) { + recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score }); + } + Recommendations.Instance.addDocuments(recDocs); + Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY); + } + onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.props.Document); }; -- cgit v1.2.3-70-g09d2 From 3f63b49ccc1ebcbeac64443702826ea4597200f3 Mon Sep 17 00:00:00 2001 From: Abdullah Ahmed Date: Mon, 16 Sep 2019 14:27:36 -0400 Subject: ui changes --- src/client/ClientRecommender.tsx | 39 ++++++++++++---------- src/client/cognitive_services/CognitiveServices.ts | 4 +-- src/client/views/nodes/DocumentView.tsx | 26 +++++++++++++-- 3 files changed, 46 insertions(+), 23 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 0e344dae9..217c89297 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -16,6 +16,12 @@ export interface RecommenderProps { title: string; } +/** + * actualDoc: datadoc + * vectorDoc: mean vector of text + * score: similarity score to main doc + */ + export interface RecommenderDocument { actualDoc: Doc; vectorDoc: number[]; @@ -28,6 +34,8 @@ export class ClientRecommender extends React.Component { static Instance: ClientRecommender; private mainDoc?: RecommenderDocument; private docVectors: Set = new Set(); + private highKP: string[] = []; + @observable private corr_matrix = [[0, 0], [0, 0]]; constructor(props: RecommenderProps) { @@ -45,10 +53,6 @@ export class ClientRecommender extends React.Component { ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]]; } - public deleteDocs() { - console.log("deleting previews..."); - } - /*** * Computes the cosine similarity between two vectors in Euclidean space. */ @@ -82,6 +86,10 @@ export class ClientRecommender extends React.Component { } } + /** + * Returns list of {doc, similarity (to main doc)} in increasing score + */ + public computeSimilarities() { ClientRecommender.Instance.docVectors.forEach((doc: RecommenderDocument) => { if (ClientRecommender.Instance.mainDoc) { @@ -127,14 +135,14 @@ export class ClientRecommender extends React.Component { * Uses Cognitive Services to extract keywords from a document */ - public async extractText(dataDoc: Doc, extDoc: Doc, mainDoc: boolean = false) { + public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, mainDoc: boolean = false) { let fielddata = Cast(dataDoc.data, RichTextField); let data: string; fielddata ? data = fielddata[ToPlainText]() : data = ""; let converter = (results: any, data: string) => { - let keyterms = new List(); - let keyterms_counted = new List(); - let highKP: string[] = [""]; + let keyterms = new List(); // raw keywords + let keyterms_counted = new List(); // keywords, where each keyword is repeated as + let highKP: string[] = [""]; // most frequent let high = 0; results.documents.forEach((doc: any) => { let keyPhrases = doc.keyPhrases; @@ -143,13 +151,12 @@ export class ClientRecommender extends React.Component { if (frequency > high) { high = frequency; highKP = [kp]; - } else if (frequency === high) { highKP.push(kp); } let words = kp.split(" "); // separates phrase into words - words = this.removeStopWords(words); + words = this.removeStopWords(words); // removes stop words if they appear in phrases words.forEach((word) => { keyterms.push(word); for (let i = 0; i < frequency; i++) { @@ -158,16 +165,12 @@ export class ClientRecommender extends React.Component { }); }); }); + this.highKP = highKP; console.log(highKP); this.sendRequest(highKP); return { keyterms: keyterms, keyterms_counted: keyterms_counted }; }; - await CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc); - } - - private findImportantKPs(keyterms_counted: string[], paragraph: string) { - let imporantSet = new Set(); - + await CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc, internal); } private countFrequencies(keyphrase: string, paragraph: string) { @@ -180,7 +183,7 @@ export class ClientRecommender extends React.Component { // console.log("Keyphrases:", kp_array); for (let i = 0; i <= par_length - num_keywords; i++) { const window = data.slice(i, i + num_keywords); - if (JSON.stringify(window) === JSON.stringify(kp_array)) { + if (JSON.stringify(window).toLowerCase() === JSON.stringify(kp_array).toLowerCase() || kp_array.every(val => window.includes(val))) { frequency++; } } @@ -194,7 +197,7 @@ export class ClientRecommender extends React.Component { private async sendRequest(keywords: string[]) { let query = ""; - keywords.forEach((kp: string) => query += kp); + keywords.forEach((kp: string) => query += " " + kp); await this.arxivrequest(query); } diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index eb1dd5197..8a58355a8 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -285,14 +285,14 @@ export namespace CognitiveServices { }); } - export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, mainDoc: boolean = false) => { + export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, mainDoc: boolean = false, internal: boolean = true) => { let results = await ExecuteQuery(Service.Text, Manager, data); console.log(results); let keyterms = converter(results, data); //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); target[keys[0]] = keyterms.keyterms; console.log("analyzed!"); - await vectorize(keyterms.keyterms_counted, dataDoc, mainDoc, data); + if (internal) await vectorize(keyterms.keyterms_counted, dataDoc, mainDoc, data); }; // export async function countFrequencies() diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 48a059b8e..f8b9cd0f1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -642,11 +642,23 @@ export class DocumentView extends DocComponent(Docu a.click(); } }); - cm.addItem({ - description: "Recommender System", + let recommender_subitems: ContextMenuProps[] = []; + + recommender_subitems.push({ + description: "Internal recommendations", event: () => this.recommender(e), icon: "brain" }); + + recommender_subitems.push({ + description: "External recommendations", + event: () => this.externalRecommendation(e), + icon: "brain" + }); + + cm.addItem({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" }); + + cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); type User = { email: string, userDocumentId: string }; let usersMenu: ContextMenuProps[] = []; @@ -729,7 +741,7 @@ export class DocumentView extends DocComponent(Docu if (!documents.includes(dataDoc)) { documents.push(dataDoc); const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, mainDoc); + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, mainDoc); } } })); @@ -743,6 +755,14 @@ export class DocumentView extends DocComponent(Docu Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY); } + externalRecommendation = async (e: React.MouseEvent) => { + if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); + ClientRecommender.Instance.reset_docs(); + const doc = Doc.GetDataDoc(this.props.Document); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false); + } + onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.props.Document); }; -- cgit v1.2.3-70-g09d2 From af59d641022119e25402f1f13ae2c3f3eb4c20a2 Mon Sep 17 00:00:00 2001 From: ab Date: Mon, 16 Sep 2019 14:39:44 -0400 Subject: initial commit --- src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 9 ++ src/client/views/GlobalKeyHandler.ts | 2 + src/client/views/MainView.tsx | 5 +- src/client/views/Recommendations.scss | 12 +- src/client/views/Recommendations.tsx | 172 +++++++++++++----------- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 20 ++- 8 files changed, 132 insertions(+), 92 deletions(-) (limited to 'src/client/views') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 1578e49fe..08f64fc8e 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -19,4 +19,5 @@ export enum DocumentType { YOUTUBE = "youtube", DRAGBOX = "dragbox", PRES = "presentation", + RECOMMENDATION = "recommendation" } \ No newline at end of file diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1e9d1687f..89358d773 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -45,6 +45,7 @@ import { PresBox } from "../views/nodes/PresBox"; import { ComputedField } from "../../new_fields/ScriptField"; import { ProxyField } from "../../new_fields/Proxy"; import { DocumentType } from "./DocumentTypes"; +import { RecommendationsBox } from "../views/Recommendations"; //import { PresBox } from "../views/nodes/PresBox"; //import { PresField } from "../../new_fields/PresField"; var requestImageSize = require('../util/request-image-size'); @@ -170,6 +171,10 @@ export namespace Docs { [DocumentType.DRAGBOX, { layout: { view: DragBox }, options: { width: 40, height: 40 }, + }], + [DocumentType.RECOMMENDATION, { + layout: { view: RecommendationsBox }, + options: { width: 200, height: 200 }, }] ]); @@ -451,6 +456,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.IMPORT), new List(), options); } + export function RecommendationsDocument(data: Doc[], options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.RECOMMENDATION), new List(data), options); + } + export type DocConfig = { doc: Doc, initialWidth?: number diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index d0464bd5f..4135afcb8 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -6,6 +6,7 @@ import { DragManager } from "../util/DragManager"; import { action, runInAction } from "mobx"; import { Doc } from "../../new_fields/Doc"; import { DictationManager } from "../util/DictationManager"; +import { RecommendationsBox } from "./Recommendations"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; @@ -72,6 +73,7 @@ export default class KeyManager { main.toggleColorPicker(true); SelectionManager.DeselectAll(); DictationManager.Controls.stop(); + // RecommendationsBox.Instance.closeMenu(); break; case "delete": case "backspace": diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 3a5795077..0bc539dca 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,7 +40,7 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; //import { DocumentManager } from '../util/DocumentManager'; -import { Recommendations } from './Recommendations'; +import { RecommendationsBox } from './Recommendations'; import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; @@ -204,7 +204,6 @@ export class MainView extends React.Component { const targets = document.elementsFromPoint(e.x, e.y); if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) { ContextMenu.Instance.closeMenu(); - Recommendations.Instance.closeMenu(); } }); @@ -567,7 +566,7 @@ export class MainView extends React.Component { {this.miniPresentation} - + {/* */} {this.nodesMenu()} {this.miscButtons} diff --git a/src/client/views/Recommendations.scss b/src/client/views/Recommendations.scss index 6619d8df3..dd8a105f6 100644 --- a/src/client/views/Recommendations.scss +++ b/src/client/views/Recommendations.scss @@ -4,7 +4,7 @@ display: inline-block; margin: auto; width: 50; - height: 30px; + height: 150px; border: 1px dashed grey; padding: 10px 10px; } @@ -19,17 +19,20 @@ overflow-y: scroll; overflow-x: hidden; position: absolute; + pointer-events: all; // display: flex; z-index: 10000; box-shadow: gray 0.2vw 0.2vw 0.4vw; // flex-direction: column; background: whitesmoke; padding-bottom: 10px; - border-radius: 15px; + padding-top: 20px; + // border-radius: 15px; border: solid #BBBBBBBB 1px; - width: 250px; + width: 100%; text-align: center; - max-height: 250px; + // max-height: 250px; + height: 100%; text-transform: uppercase; color: grey; letter-spacing: 2px; @@ -48,7 +51,6 @@ background-color: transparent; width: 50%; text-align: center; - height: 35px; margin-left: 5px; } diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx index d0105ee18..ff6e66492 100644 --- a/src/client/views/Recommendations.tsx +++ b/src/client/views/Recommendations.tsx @@ -14,27 +14,37 @@ import { DocumentType } from '../documents/DocumentTypes'; import { ClientRecommender } from "../ClientRecommender"; import { DocServer } from "../DocServer"; import { Id } from "../../new_fields/FieldSymbols"; +import { FieldView, FieldViewProps } from "./nodes/FieldView"; +import { DocumentManager } from "../util/DocumentManager"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons"; +import { DocUtils } from "../documents/Documents"; export interface RecProps { documents: { preview: Doc, similarity: number }[]; node: Doc; } +library.add(faBullseye, faLink); + @observer -export class Recommendations extends React.Component<{}> { +export class RecommendationsBox extends React.Component { + + public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(RecommendationsBox, fieldKey); } - static Instance: Recommendations; - @observable private _display: boolean = false; + static Instance: RecommendationsBox; + // @observable private _display: boolean = false; @observable private _pageX: number = 0; @observable private _pageY: number = 0; @observable private _width: number = 0; @observable private _height: number = 0; - @observable private _documents: { preview: Doc, score: number }[] = []; + // @observable private _documents: { preview: Doc, score: number }[] = []; private previewDocs: Doc[] = []; - constructor(props: {}) { + constructor(props: FieldViewProps) { super(props); - Recommendations.Instance = this; + RecommendationsBox.Instance = this; } private DocumentIcon(doc: Doc) { @@ -52,12 +62,12 @@ export class Recommendations extends React.Component<{}> { }; }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); } - let returnXDimension = () => 50; - let returnYDimension = () => 50; + let returnXDimension = () => 150; + let returnYDimension = () => 150; let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); //let scale = () => 1; let newRenderDoc = Doc.MakeAlias(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt - newRenderDoc.height = 50; + newRenderDoc.height = NumCast(this.props.Document.documentIconHeight); newRenderDoc.autoHeight = false; const docview =
{/* onPointerDown={action(() => { @@ -78,7 +88,8 @@ export class Recommendations extends React.Component<{}> { PanelHeight={returnYDimension} focus={emptyFunction} backgroundColor={returnEmptyString} - selectOnLoad={false} + // selectOnLoad={false} + pinToPres={emptyFunction} parentActive={returnFalse} whenActiveChanged={returnFalse} bringToFront={emptyFunction} @@ -96,83 +107,84 @@ export class Recommendations extends React.Component<{}> { } - @action - closeMenu = () => { - this._display = false; - this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id])); - this.previewDocs = []; - } - - @action - resetDocuments = () => { - this._documents = []; - } - - @action - addDocuments = (documents: { preview: Doc, score: number }[]) => { - this._documents = documents; - } - - @action - displayRecommendations(x: number, y: number) { - this._pageX = x; - this._pageY = y; - this._display = true; - } + // @action + // closeMenu = () => { + // this._display = false; + // this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id])); + // this.previewDocs = []; + // } + + // @action + // resetDocuments = () => { + // this._documents = []; + // } + + // @action + // displayRecommendations(x: number, y: number) { + // this._pageX = x; + // this._pageY = y; + // this._display = true; + // } static readonly buffer = 20; - get pageX() { - const x = this._pageX; - if (x < 0) { - return 0; - } - const width = this._width; - if (x + width > window.innerWidth - Recommendations.buffer) { - return window.innerWidth - Recommendations.buffer - width; - } - return x; - } - - get pageY() { - const y = this._pageY; - if (y < 0) { - return 0; - } - const height = this._height; - if (y + height > window.innerHeight - Recommendations.buffer) { - return window.innerHeight - Recommendations.buffer - height; - } - return y; - } + // get pageX() { + // const x = this._pageX; + // if (x < 0) { + // return 0; + // } + // const width = this._width; + // if (x + width > window.innerWidth - RecommendationsBox.buffer) { + // return window.innerWidth - RecommendationsBox.buffer - width; + // } + // return x; + // } + + // get pageY() { + // const y = this._pageY; + // if (y < 0) { + // return 0; + // } + // const height = this._height; + // if (y + height > window.innerHeight - RecommendationsBox.buffer) { + // return window.innerHeight - RecommendationsBox.buffer - height; + // } + // return y; + // } render() { - if (!this._display) { - return null; - } - let style = { left: this.pageX, top: this.pageY }; + // if (!this._display) { + // return null; + // } + // let style = { left: this.pageX, top: this.pageY }; //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" return ( - { this._width = r.offset.width; this._height = r.offset.height; })}> - {({ measureRef }) => ( -
-

Recommendations

- {this._documents.map(doc => { - return ( -
- - {this.DocumentIcon(doc.preview)} - - {doc.score.toFixed(4)} -
- ); - })} - -
- ) - } - -
+ // { this._width = r.offset.width; this._height = r.offset.height; })}> + // {({ measureRef }) => ( +
+

Recommendations

+ {DocListCast(this.props.Document.data).map(doc => { + return ( +
+ + {this.DocumentIcon(doc)} + + {NumCast(doc.score).toFixed(4)} +
DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, this.props.Document.sourceDocContext as Doc)}> + +
+
DocUtils.MakeLink(this.props.Document.sourceDoc as Doc, doc, undefined, "User Selected Link", "Generated from Recommender", undefined)}> + +
+
+ ); + })} + +
+ // ); + // } + + //
); } } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d77662355..c8a636727 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -29,6 +29,7 @@ import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; import { List } from "../../../new_fields/List"; import { Doc } from "../../../new_fields/Doc"; import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; +import { RecommendationsBox } from "../../views/Recommendations"; import { ScriptField } from "../../../new_fields/ScriptField"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -108,7 +109,7 @@ export class DocumentContentsView extends React.Component(Docu for (let i = 0; i < doclist.length; i++) { recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score }); } - Recommendations.Instance.addDocuments(recDocs); - Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY); + + const data = recDocs.map(unit => { + unit.preview.score = unit.score; + return unit.preview; + }); + + console.log(recDocs.map(doc => doc.score)); + + const title = `Showing ${data.length} recommendations for "${StrCast(this.props.Document.title)}"`; + const recommendations = Docs.Create.RecommendationsDocument(data, { title }); + recommendations.documentIconHeight = 150; + recommendations.sourceDoc = this.props.Document; + recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document; + CollectionDockingView.Instance.AddRightSplit(recommendations, undefined); + + // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY); } onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; -- cgit v1.2.3-70-g09d2 From 19375927c677ad6c99c77d0c7dac17fe7a2712a9 Mon Sep 17 00:00:00 2001 From: ab Date: Mon, 16 Sep 2019 15:26:36 -0400 Subject: beginning to handle external interactions --- src/client/ClientRecommender.tsx | 26 +++++++++++++--------- src/client/cognitive_services/CognitiveServices.ts | 12 ++++++---- src/client/views/Recommendations.tsx | 6 ++++- .../views/collections/CollectionSchemaCells.tsx | 16 ++++++++++++- src/client/views/nodes/DocumentView.tsx | 7 +++++- 5 files changed, 49 insertions(+), 18 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 217c89297..551047df0 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -139,7 +139,7 @@ export class ClientRecommender extends React.Component { let fielddata = Cast(dataDoc.data, RichTextField); let data: string; fielddata ? data = fielddata[ToPlainText]() : data = ""; - let converter = (results: any, data: string) => { + let converter = async (results: any, data: string) => { let keyterms = new List(); // raw keywords let keyterms_counted = new List(); // keywords, where each keyword is repeated as let highKP: string[] = [""]; // most frequent @@ -167,10 +167,10 @@ export class ClientRecommender extends React.Component { }); this.highKP = highKP; console.log(highKP); - this.sendRequest(highKP); - return { keyterms: keyterms, keyterms_counted: keyterms_counted }; + const values = await this.sendRequest(highKP); + return { keyterms: keyterms, keyterms_counted: keyterms_counted, values }; }; - await CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc, internal); + return CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc, internal); } private countFrequencies(keyphrase: string, paragraph: string) { @@ -198,7 +198,9 @@ export class ClientRecommender extends React.Component { private async sendRequest(keywords: string[]) { let query = ""; keywords.forEach((kp: string) => query += " " + kp); - await this.arxivrequest(query); + return new Promise(resolve => { + this.arxivrequest(query).then(resolve); + }); } /** @@ -207,7 +209,7 @@ export class ClientRecommender extends React.Component { arxivrequest = async (query: string) => { let xhttp = new XMLHttpRequest(); - let serveraddress = "http://export.arxiv.org/api" + let serveraddress = "http://export.arxiv.org/api"; let endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=1"; let promisified = (resolve: any, reject: any) => { xhttp.onreadystatechange = function () { @@ -217,20 +219,22 @@ export class ClientRecommender extends React.Component { console.log(xml); switch (this.status) { case 200: + let title: string = "Title"; + let url: string = "Url"; //console.log(result); if (xml) { let titles = xml.getElementsByTagName("title"); if (titles && titles.length > 1) { - let text = titles[1].childNodes[0].nodeValue; - console.log(text); + title = titles[1].childNodes[0].nodeValue!; + console.log(title); } let ids = xml.getElementsByTagName("id"); if (ids && ids.length > 1) { - let text = ids[1].childNodes[0].nodeValue; - console.log(text); + url = ids[1].childNodes[0].nodeValue!; + console.log(url); } } - return resolve(result); + return resolve({ title, url }); case 400: default: return reject(result); diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 8a58355a8..baafb63a1 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -15,7 +15,7 @@ type RequestExecutor = (apiKey: string, body: string, service: Service) => Promi type AnalysisApplier = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any; type BodyConverter = (data: D) => string; type Converter = (results: any) => Field; -type TextConverter = (results: any, data: string) => { keyterms: Field, keyterms_counted: Field }; +type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field, keyterms_counted: Field, values: any }>; export type Tag = { name: string, confidence: number }; export type Rectangle = { top: number, left: number, width: number, height: number }; @@ -288,11 +288,15 @@ export namespace CognitiveServices { export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, mainDoc: boolean = false, internal: boolean = true) => { let results = await ExecuteQuery(Service.Text, Manager, data); console.log(results); - let keyterms = converter(results, data); + let { keyterms, values, keyterms_counted } = await converter(results, data); //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); - target[keys[0]] = keyterms.keyterms; + target[keys[0]] = keyterms; console.log("analyzed!"); - if (internal) await vectorize(keyterms.keyterms_counted, dataDoc, mainDoc, data); + if (internal) { + await vectorize(keyterms_counted, dataDoc, mainDoc, data); + } else { + return values; + } }; // export async function countFrequencies() diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx index ff6e66492..b7b1d84d0 100644 --- a/src/client/views/Recommendations.tsx +++ b/src/client/views/Recommendations.tsx @@ -158,11 +158,15 @@ export class RecommendationsBox extends React.Component { // } // let style = { left: this.pageX, top: this.pageY }; //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" + let title = StrCast((this.props.Document.sourceDoc as Doc).title); + if (title.length > 15) { + title = title.substring(0, 15) + "..."; + } return ( // { this._width = r.offset.width; this._height = r.offset.height; })}> // {({ measureRef }) => (
-

Recommendations

+

Recommendations for "{title}"

{DocListCast(this.props.Document.data).map(doc => { return (
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 9c26a08f0..bf8c4b6f7 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -27,6 +27,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { KeyCodes } from "../../northstar/utils/KeyCodes"; import { undoBatch } from "../../util/UndoManager"; +import { List } from "lodash"; library.add(faExpand); @@ -86,10 +87,23 @@ export class CollectionSchemaCell extends React.Component { } @action - onPointerDown = (e: React.PointerEvent): void => { + onPointerDown = async (e: React.PointerEvent): Promise => { this.props.changeFocusedCellByIndex(this.props.row, this.props.col); this.props.setPreviewDoc(this.props.rowProps.original); + const data = await DocListCastAsync(this.props.Document.data); + if (data) { + let url: string; + if (url = StrCast(data[0].href)) { + try { + new URL(url); + const temp = window.open(url)!; + temp.blur(); + window.focus(); + } catch { } + } + } + // this._isEditing = true; // this.props.setIsEditing(true); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2ae71f1da..a034bc1f4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -45,6 +45,7 @@ import { RecommendationsBox } from '../Recommendations'; import { SearchUtil } from '../../util/SearchUtil'; import { ClientRecommender } from '../../ClientRecommender'; import { DocumentType } from '../../documents/DocumentTypes'; +import { SchemaHeaderField } from '../../../new_fields/SchemaHeaderField'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faBrain); @@ -774,7 +775,11 @@ export class DocumentView extends DocComponent(Docu ClientRecommender.Instance.reset_docs(); const doc = Doc.GetDataDoc(this.props.Document); const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false); + const values = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false); + const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")]; + const body = Docs.Create.FreeformDocument([], { title: values.title }); + body.href = values.url; + CollectionDockingView.Instance.AddRightSplit(Docs.Create.SchemaDocument(headers, [body], { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); } onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; -- cgit v1.2.3-70-g09d2 From e0b84cf329d7a9c9bda1aadbf25894c4bcc8f13a Mon Sep 17 00:00:00 2001 From: ab Date: Mon, 16 Sep 2019 17:15:37 -0400 Subject: demo touches --- src/client/ClientRecommender.tsx | 26 +++++++++++++++------- .../views/collections/CollectionSchemaCells.tsx | 19 +++++++--------- src/client/views/nodes/DocumentView.tsx | 13 ++++++++--- 3 files changed, 36 insertions(+), 22 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 103993a67..14af0a69b 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -212,7 +212,7 @@ export class ClientRecommender extends React.Component { arxivrequest = async (query: string) => { let xhttp = new XMLHttpRequest(); let serveraddress = "http://export.arxiv.org/api"; - let endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=1"; + let endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=5"; let promisified = (resolve: any, reject: any) => { xhttp.onreadystatechange = function () { if (this.readyState === 4) { @@ -221,22 +221,32 @@ export class ClientRecommender extends React.Component { console.log(xml); switch (this.status) { case 200: - let title: string = "Title"; - let url: string = "Url"; + let title_vals: string[] = []; + let url_vals: string[] = []; //console.log(result); if (xml) { let titles = xml.getElementsByTagName("title"); + let counter = 1; if (titles && titles.length > 1) { - title = titles[1].childNodes[0].nodeValue!; - console.log(title); + while (counter <= 5) { + const title = titles[counter].childNodes[0].nodeValue!; + console.log(title) + title_vals.push(title); + counter++; + } } let ids = xml.getElementsByTagName("id"); + counter = 1; if (ids && ids.length > 1) { - url = ids[1].childNodes[0].nodeValue!; - console.log(url); + while (counter <= 5) { + const url = ids[counter].childNodes[0].nodeValue!; + console.log(url); + url_vals.push(url); + counter++; + } } } - return resolve({ title, url }); + return resolve({ title_vals, url_vals }); case 400: default: return reject(result); diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index bf8c4b6f7..511fbc3a0 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -91,17 +91,14 @@ export class CollectionSchemaCell extends React.Component { this.props.changeFocusedCellByIndex(this.props.row, this.props.col); this.props.setPreviewDoc(this.props.rowProps.original); - const data = await DocListCastAsync(this.props.Document.data); - if (data) { - let url: string; - if (url = StrCast(data[0].href)) { - try { - new URL(url); - const temp = window.open(url)!; - temp.blur(); - window.focus(); - } catch { } - } + let url: string; + if (url = StrCast(this.props.rowProps.row.href)) { + try { + new URL(url); + const temp = window.open(url)!; + temp.blur(); + window.focus(); + } catch { } } // this._isEditing = true; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a034bc1f4..a80eafde2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -777,9 +777,16 @@ export class DocumentView extends DocComponent(Docu const extdoc = doc.data_ext as Doc; const values = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false); const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")]; - const body = Docs.Create.FreeformDocument([], { title: values.title }); - body.href = values.url; - CollectionDockingView.Instance.AddRightSplit(Docs.Create.SchemaDocument(headers, [body], { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); + let bodies: Doc[] = []; + const titles = values.title_vals; + const urls = values.url_vals; + for (let i = 0; i < 5; i++) { + const body = Docs.Create.FreeformDocument([], { title: titles[i] }); + body.href = urls[i]; + bodies.push(body); + } + + CollectionDockingView.Instance.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); } onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; -- cgit v1.2.3-70-g09d2 From 7a01cf9c12f850c0c1d9c278452df88dd55845aa Mon Sep 17 00:00:00 2001 From: ab Date: Sat, 21 Sep 2019 16:54:56 -0400 Subject: parsed 1m file --- package.json | 2 +- sentence_parser.py | 7 +++++++ src/client/views/nodes/DocumentView.tsx | 1 - src/server/Recommender.ts | 16 ++++++++++++++++ src/server/index.ts | 1 + 5 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 sentence_parser.py (limited to 'src/client/views') diff --git a/package.json b/package.json index 3486f752b..422f20e79 100644 --- a/package.json +++ b/package.json @@ -229,4 +229,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} +} \ No newline at end of file diff --git a/sentence_parser.py b/sentence_parser.py new file mode 100644 index 000000000..a07b25f6d --- /dev/null +++ b/sentence_parser.py @@ -0,0 +1,7 @@ +def parse_text(file_path): + with open(file_path, encoding='utf8',mode='r+') as f: + lines = f.readlines() + print(len(lines)) + print(lines[1][1]) + +parse_text("eng_news-typical_2016_10K-sentences.txt") \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a80eafde2..c383163e8 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -785,7 +785,6 @@ export class DocumentView extends DocComponent(Docu body.href = urls[i]; bodies.push(body); } - CollectionDockingView.Instance.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); } diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index d762da5b2..d47257550 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -79,6 +79,22 @@ export class Recommender { } } + public async trainModel() { + console.log("phrasing..."); + w2v.word2vec("./node_modules/word2vec/examples/eng_news-typical_2016_1M-sentences.txt", './node_modules/word2vec/examples/my_phrases.txt', { + cbow: 1, + size: 200, + window: 8, + negative: 25, + hs: 0, + sample: 1e-4, + threads: 20, + iter: 200, + minCount: 2 + }); + console.log("phrased!!!"); + } + public async arxivRequest(query: string) { // let xhttp = new XMLHttpRequest(); // let serveraddress = "http://export.arxiv.org/api/query?search_query=all:electron&start=0&max_results=1"; diff --git a/src/server/index.ts b/src/server/index.ts index 24ffc466f..e7d49579d 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -688,6 +688,7 @@ app.use(RouteStore.corsProxy, (req, res) => { let recommender = new Recommender(); recommender.testModel(); +recommender.trainModel(); recommender.arxivRequest("Triangle-GAN"); app.post("/recommender", async (req, res) => { -- cgit v1.2.3-70-g09d2 From c6e2eee8e5b035ed4cab1e7bb1315a36d43255c5 Mon Sep 17 00:00:00 2001 From: ab Date: Tue, 8 Oct 2019 16:02:25 -0400 Subject: switching out --- src/client/views/MainView.tsx | 1 - src/client/views/Recommendations.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 --- src/client/views/nodes/DocumentContentsView.tsx | 6 ------ src/client/views/nodes/DocumentView.tsx | 4 ++-- src/client/views/nodes/ImageBox.tsx | 1 - src/new_fields/RichTextField.ts | 17 ++++++++++++++++- 7 files changed, 19 insertions(+), 15 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 17de708a2..e6f500e75 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -41,7 +41,6 @@ import { FilterBox } from './search/FilterBox'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; //import { DocumentManager } from '../util/DocumentManager'; import { RecommendationsBox } from './Recommendations'; -import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; import { OverlayView } from './OverlayView'; diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx index b7b1d84d0..c44dfc032 100644 --- a/src/client/views/Recommendations.tsx +++ b/src/client/views/Recommendations.tsx @@ -174,7 +174,7 @@ export class RecommendationsBox extends React.Component { {this.DocumentIcon(doc)} {NumCast(doc.score).toFixed(4)} -
DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, this.props.Document.sourceDocContext as Doc)}> +
DocumentManager.Instance.jumpToDocument(doc, false)}>
DocUtils.MakeLink(this.props.Document.sourceDoc as Doc, doc, undefined, "User Selected Link", "Generated from Recommender", undefined)}> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3ee069e4c..d2b8afa02 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -12,7 +12,6 @@ import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fiel import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; import { aggregateBounds, emptyFunction, intersectRect, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; -import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -28,7 +27,6 @@ import { InkingCanvas } from "../../InkingCanvas"; import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView"; -import { FormattedTextBox } from "../../nodes/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import PDFMenu from "../../pdf/PDFMenu"; import { CollectionSubView } from "../CollectionSubView"; @@ -41,7 +39,6 @@ import React = require("react"); import v5 = require("uuid/v5"); import { ClientRecommender } from "../../../ClientRecommender"; import { SearchUtil } from "../../../util/SearchUtil"; -import { SearchBox } from "../../SearchBox"; import { RouteStore } from "../../../../server/RouteStore"; import { string, number, elementType } from "prop-types"; import { DocServer } from "../../../DocServer"; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e5eb8dbce..919c13055 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -2,8 +2,6 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../new_fields/Doc"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast } from "../../../new_fields/Types"; -import { OmitKeys, Without } from "../../../Utils"; import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; import { CollectionDockingView } from "../collections/CollectionDockingView"; @@ -30,14 +28,10 @@ import { PresElementBox } from "../presentationview/PresElementBox"; import { VideoBox } from "./VideoBox"; import { WebBox } from "./WebBox"; import React = require("react"); -import { FieldViewProps } from "./FieldView"; import { Without, OmitKeys } from "../../../Utils"; import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; import { List } from "../../../new_fields/List"; -import { Doc } from "../../../new_fields/Doc"; -import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; import { RecommendationsBox } from "../../views/Recommendations"; -import { ScriptField } from "../../../new_fields/ScriptField"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? type BindingProps = Without; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0064b98c3..070b1f426 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -621,7 +621,7 @@ export class DocumentView extends DocComponent(Docu recommendations.documentIconHeight = 150; recommendations.sourceDoc = this.props.Document; recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document; - CollectionDockingView.Instance.AddRightSplit(recommendations, undefined); + CollectionDockingView.AddRightSplit(recommendations, undefined); // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY); } @@ -641,7 +641,7 @@ export class DocumentView extends DocComponent(Docu body.href = urls[i]; bodies.push(body); } - CollectionDockingView.Instance.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); + CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); } onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index f36b9895f..7ffe64b9b 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -29,7 +29,6 @@ import "./ImageBox.scss"; import React = require("react"); import { SearchUtil } from '../../util/SearchUtil'; import { ClientRecommender } from '../../ClientRecommender'; -import { DocumentType } from '../../documents/Documents'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index 390045ee1..f41ea0350 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -4,6 +4,9 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { Copy, ToScriptString, ToPlainText } from "./FieldSymbols"; import { scriptingGlobal } from "../client/util/Scripting"; +const delimiter = "\n"; +const joiner = ""; + @scriptingGlobal @Deserializable("RichTextField") export class RichTextField extends ObjectField { @@ -24,7 +27,19 @@ export class RichTextField extends ObjectField { } [ToPlainText]() { - return this.Data; + // Because we're working with plain text, just concatenate all paragraphs + let content = JSON.parse(this.Data).doc.content; + let paragraphs = content.filter((item: any) => item.type === "paragraph"); + + // Functions to flatten ProseMirror paragraph objects (and their components) to plain text + // While this function already exists in state.doc.textBeteen(), it doesn't account for newlines + let blockText = (block: any) => block.text; + let concatenateParagraph = (p: any) => (p.content ? p.content.map(blockText).join(joiner) : "") + delimiter; + + // Concatentate paragraphs and string the result together + let textParagraphs: string[] = paragraphs.map(concatenateParagraph); + let plainText = textParagraphs.join(joiner); + return plainText.substring(0, plainText.length - 1); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From b2434c6f938bf464c8c18c2e6f9d9c171a1aee7e Mon Sep 17 00:00:00 2001 From: Abdullah Ahmed Date: Wed, 9 Oct 2019 16:10:16 -0400 Subject: changed way vectors are processed --- src/client/ClientRecommender.tsx | 14 ++++++++++++-- src/client/cognitive_services/CognitiveServices.ts | 8 ++++---- src/client/util/SearchUtil.ts | 2 +- src/client/views/Recommendations.tsx | 17 +++++++++-------- 4 files changed, 26 insertions(+), 15 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index b4a496563..9ce7df366 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -126,6 +126,14 @@ export class ClientRecommender extends React.Component { return meanVector; } + public processVector(vector: number[], dataDoc: Doc, mainDoc: boolean) { + if (vector.length > 0) { + const internalDoc: RecommenderDocument = { actualDoc: dataDoc, vectorDoc: vector, score: 0 }; + if (mainDoc) ClientRecommender.Instance.mainDoc = internalDoc; + ClientRecommender.Instance.addToDocSet(internalDoc); + } + } + private addToDocSet(internalDoc: RecommenderDocument) { if (ClientRecommender.Instance.docVectors) { ClientRecommender.Instance.docVectors.add(internalDoc); @@ -149,6 +157,7 @@ export class ClientRecommender extends React.Component { let keyPhrases = doc.keyPhrases; keyPhrases.map((kp: string) => { const frequency = this.countFrequencies(kp, data); + keyterms.push(kp); if (frequency > high) { high = frequency; highKP = [kp]; @@ -159,7 +168,7 @@ export class ClientRecommender extends React.Component { let words = kp.split(" "); // separates phrase into words words = this.removeStopWords(words); // removes stop words if they appear in phrases words.forEach((word) => { - keyterms.push(word); + //keyterms.push(word); for (let i = 0; i < frequency; i++) { keyterms_counted.push(word); } @@ -170,7 +179,8 @@ export class ClientRecommender extends React.Component { //console.log(highKP); const kts_counted = new List(); keyterms_counted.forEach(kt => kts_counted.push(kt.toLowerCase())); - const values = await this.sendRequest(highKP); + let values = ""; + if (!internal) values = await this.sendRequest(highKP); return { keyterms: keyterms, keyterms_counted: kts_counted, values }; }; if (data != "") { diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index e391b98d2..c138c68b7 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -272,12 +272,12 @@ export namespace CognitiveServices { if (wordvecs) { let indices = Object.keys(wordvecs); console.log("successful vectorization!"); - var vectorValues = new Set(); + var vectorValues = new List(); indices.forEach((ind: any) => { //console.log(wordvec.word); - vectorValues.add(wordvecs[ind]); + vectorValues.push(wordvecs[ind]); }); - ClientRecommender.Instance.mean(vectorValues, dataDoc, mainDoc); + ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc); } // adds document to internal doc set else { console.log("unsuccessful :( word(s) not in vocabulary"); @@ -295,7 +295,7 @@ export namespace CognitiveServices { target[keys[0]] = keyterms; console.log("analyzed!"); if (internal) { - await vectorize(keyterms_counted, dataDoc, mainDoc, data); + await vectorize(keyterms, dataDoc, mainDoc, data); } else { return values; } diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index d65ec3f40..fb3189069 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -127,7 +127,7 @@ export namespace SearchUtil { }); let result: IdSearchResult = JSON.parse(response); const { ids, numFound, highlighting } = result; - console.log(ids.length); + //console.log(ids.length); const docMap = await DocServer.GetRefFields(ids); const docs: Doc[] = []; for (const id of ids) { diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx index c44dfc032..5dc62105d 100644 --- a/src/client/views/Recommendations.tsx +++ b/src/client/views/Recommendations.tsx @@ -77,25 +77,26 @@ export class RecommendationsBox extends React.Component { onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} onPointerLeave={action(() => this._displayDim = 50)} > */}
; @@ -170,16 +171,16 @@ export class RecommendationsBox extends React.Component { {DocListCast(this.props.Document.data).map(doc => { return (
- + {/* {this.DocumentIcon(doc)} - + */} {NumCast(doc.score).toFixed(4)}
DocumentManager.Instance.jumpToDocument(doc, false)}>
-
DocUtils.MakeLink(this.props.Document.sourceDoc as Doc, doc, undefined, "User Selected Link", "Generated from Recommender", undefined)}> + {/*
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> -
+
*/}
); })} -- cgit v1.2.3-70-g09d2 From 3a2ade7686c587ce8666d5c8be7a0be432d025d3 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 9 Oct 2019 17:09:29 -0400 Subject: fixed JSX parser registration for recommendation box in document contents view --- src/client/views/nodes/DocumentContentsView.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 919c13055..43abc30a1 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -29,8 +29,7 @@ import { VideoBox } from "./VideoBox"; import { WebBox } from "./WebBox"; import React = require("react"); import { Without, OmitKeys } from "../../../Utils"; -import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; -import { List } from "../../../new_fields/List"; +import { Cast } from "../../../new_fields/Types"; import { RecommendationsBox } from "../../views/Recommendations"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -103,7 +102,7 @@ export class DocumentContentsView extends React.Component Date: Fri, 11 Oct 2019 17:40:09 -0400 Subject: fixes + refactoring --- src/client/ClientRecommender.tsx | 95 ++++++---------------- src/client/cognitive_services/CognitiveServices.ts | 6 +- src/client/views/Recommendations.tsx | 22 ++--- src/server/Recommender.ts | 2 +- 4 files changed, 37 insertions(+), 88 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 9ce7df366..bc1cd139c 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -35,7 +35,6 @@ export class ClientRecommender extends React.Component { static Instance: ClientRecommender; private mainDoc?: RecommenderDocument; private docVectors: Set = new Set(); - private highKP: string[] = []; @observable private corr_matrix = [[0, 0], [0, 0]]; @@ -94,7 +93,7 @@ export class ClientRecommender extends React.Component { public computeSimilarities() { ClientRecommender.Instance.docVectors.forEach((doc: RecommenderDocument) => { if (ClientRecommender.Instance.mainDoc) { - const distance = ClientRecommender.Instance.distance(ClientRecommender.Instance.mainDoc.vectorDoc, doc.vectorDoc, "euclidian"); + const distance = ClientRecommender.Instance.distance(ClientRecommender.Instance.mainDoc.vectorDoc, doc.vectorDoc, "cosine"); doc.score = distance; } } @@ -151,17 +150,21 @@ export class ClientRecommender extends React.Component { let converter = async (results: any, data: string) => { let keyterms = new List(); // raw keywords let keyterms_counted = new List(); // keywords, where each keyword is repeated as - let highKP: string[] = [""]; // most frequent + let kp_string: string = ""; + let highKP: string[] = [""]; // most frequent keyphrase let high = 0; results.documents.forEach((doc: any) => { let keyPhrases = doc.keyPhrases; keyPhrases.map((kp: string) => { const frequency = this.countFrequencies(kp, data); keyterms.push(kp); + kp_string += kp + ", "; + // replaces highKP with new one if (frequency > high) { high = frequency; highKP = [kp]; } + // appends to current highKP phrase else if (frequency === high) { highKP.push(kp); } @@ -175,13 +178,11 @@ export class ClientRecommender extends React.Component { }); }); }); - this.highKP = highKP; - //console.log(highKP); const kts_counted = new List(); keyterms_counted.forEach(kt => kts_counted.push(kt.toLowerCase())); let values = ""; if (!internal) values = await this.sendRequest(highKP); - return { keyterms: keyterms, keyterms_counted: kts_counted, values }; + return { keyterms: keyterms, keyterms_counted: kts_counted, values, kp_string: [kp_string] }; }; if (data != "") { return CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc, internal); @@ -189,6 +190,10 @@ export class ClientRecommender extends React.Component { return; } + /** + * + * Counts frequencies of keyphrase in paragraph. + */ private countFrequencies(keyphrase: string, paragraph: string) { let data = paragraph.split(" "); @@ -196,8 +201,7 @@ export class ClientRecommender extends React.Component { let num_keywords = kp_array.length; let par_length = data.length; let frequency = 0; - // console.log("Paragraph: ", data); - // console.log("Keyphrases:", kp_array); + // slides keyphrase windows across paragraph and checks if it matches with corresponding paragraph slice for (let i = 0; i <= par_length - num_keywords; i++) { const window = data.slice(i, i + num_keywords); if (JSON.stringify(window).toLowerCase() === JSON.stringify(kp_array).toLowerCase() || kp_array.every(val => window.includes(val))) { @@ -207,11 +211,21 @@ export class ClientRecommender extends React.Component { return frequency; } + /** + * + * Removes stopwords from list of strings representing a sentence + */ + private removeStopWords(word_array: string[]) { //console.log(sw.removeStopwords(word_array)); return sw.removeStopwords(word_array); } + /** + * + * API for sending arXiv request. + */ + private async sendRequest(keywords: string[]) { let query = ""; keywords.forEach((kp: string) => query += " " + kp); @@ -221,13 +235,14 @@ export class ClientRecommender extends React.Component { } /** - * Request to the arXiv server for ML articles. + * Actual request to the arXiv server for ML articles. */ arxivrequest = async (query: string) => { let xhttp = new XMLHttpRequest(); let serveraddress = "http://export.arxiv.org/api"; - let endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=5"; + const maxresults = 5; + let endpoint = serveraddress + "/query?search_query=all:" + query + "&start=0&max_results=" + maxresults.toString(); let promisified = (resolve: any, reject: any) => { xhttp.onreadystatechange = function () { if (this.readyState === 4) { @@ -243,7 +258,7 @@ export class ClientRecommender extends React.Component { let titles = xml.getElementsByTagName("title"); let counter = 1; if (titles && titles.length > 1) { - while (counter <= 5) { + while (counter <= maxresults) { const title = titles[counter].childNodes[0].nodeValue!; console.log(title) title_vals.push(title); @@ -253,7 +268,7 @@ export class ClientRecommender extends React.Component { let ids = xml.getElementsByTagName("id"); counter = 1; if (ids && ids.length > 1) { - while (counter <= 5) { + while (counter <= maxresults) { const url = ids[counter].childNodes[0].nodeValue!; console.log(url); url_vals.push(url); @@ -280,64 +295,8 @@ export class ClientRecommender extends React.Component { console.log(text); } - /*** - * Creates distance matrix for all Documents analyzed - */ - - @action - public createDistanceMatrix(documents: Set = ClientRecommender.Instance.docVectors) { - const documents_list = Array.from(documents); - const n = documents_list.length; - var matrix = new Array(n).fill(0).map(() => new Array(n).fill(0)); - for (let i = 0; i < n; i++) { - var doc1 = documents_list[i]; - for (let j = 0; j < n; j++) { - var doc2 = documents_list[j]; - matrix[i][j] = ClientRecommender.Instance.distance(doc1.vectorDoc, doc2.vectorDoc, "euclidian"); - } - } - ClientRecommender.Instance.corr_matrix = matrix; - return matrix; - } - - @computed - private get generateRows() { - const n = ClientRecommender.Instance.corr_matrix.length; - let rows: JSX.Element[] = []; - for (let i = 0; i < n; i++) { - let children: JSX.Element[] = []; - for (let j = 0; j < n; j++) { - //let cell = React.createElement("td", ClientRecommender.Instance.corr_matrix[i][j]); - let cell = {ClientRecommender.Instance.corr_matrix[i][j].toFixed(4)}; - children.push(cell); - } - //let row = React.createElement("tr", { children: children, key: i }); - let row = {children}; - rows.push(row); - } - return rows; - } - render() { return (
-

{ClientRecommender.Instance.props.title ? ClientRecommender.Instance.props.title : "hello"}

- {/* - - - - - - - - - - -
{ClientRecommender.Instance.corr_matrix[0][0].toFixed(4)}{ClientRecommender.Instance.corr_matrix[0][1].toFixed(4)}
{ClientRecommender.Instance.corr_matrix[1][0].toFixed(4)}{ClientRecommender.Instance.corr_matrix[1][1].toFixed(4)}
*/} - - - {ClientRecommender.Instance.generateRows} - -
); } diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index c138c68b7..eb088763d 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -15,7 +15,7 @@ type RequestExecutor = (apiKey: string, body: string, service: Service) => Promi type AnalysisApplier = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any; type BodyConverter = (data: D) => string; type Converter = (results: any) => Field; -type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field, keyterms_counted: Field, values: any }>; +type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field, keyterms_counted: Field, values: any, kp_string: string[] }>; export type Tag = { name: string, confidence: number }; export type Rectangle = { top: number, left: number, width: number, height: number }; @@ -290,12 +290,12 @@ export namespace CognitiveServices { export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, mainDoc: boolean = false, internal: boolean = true) => { let results = await ExecuteQuery(Service.Text, Manager, data); console.log(results); - let { keyterms, values, keyterms_counted } = await converter(results, data); + let { keyterms, values, keyterms_counted, kp_string } = await converter(results, data); //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); target[keys[0]] = keyterms; console.log("analyzed!"); if (internal) { - await vectorize(keyterms, dataDoc, mainDoc, data); + await vectorize(kp_string, dataDoc, mainDoc, data); } else { return values; } diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx index 5dc62105d..f965d655b 100644 --- a/src/client/views/Recommendations.tsx +++ b/src/client/views/Recommendations.tsx @@ -70,15 +70,9 @@ export class RecommendationsBox extends React.Component { newRenderDoc.height = NumCast(this.props.Document.documentIconHeight); newRenderDoc.autoHeight = false; const docview =
- {/* onPointerDown={action(() => { - this._useIcons = !this._useIcons; - this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); - })} - onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} - onPointerLeave={action(() => this._displayDim = 50)} > */} { ContentScaling={scale} />
; - // const data = renderDoc.data; - // if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); - // newRenderDoc.preview = true; - // this.previewDocs.push(newRenderDoc); return docview; } @@ -171,16 +161,16 @@ export class RecommendationsBox extends React.Component { {DocListCast(this.props.Document.data).map(doc => { return (
- {/* + {this.DocumentIcon(doc)} - */} + {NumCast(doc.score).toFixed(4)}
DocumentManager.Instance.jumpToDocument(doc, false)}>
- {/*
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> +
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> -
*/} +
); })} diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts index d014ba344..d974d7ef6 100644 --- a/src/server/Recommender.ts +++ b/src/server/Recommender.ts @@ -17,7 +17,7 @@ export class Recommender { private _model: any; static Instance: Recommender; private dimension: number = 0; - private choice: string = ""; + private choice: string = ""; // Tensorflow or Word2Vec constructor() { console.log("creating recommender..."); -- cgit v1.2.3-70-g09d2 From 814838063b6bbdf8dc813eb601de8da6b4ae0320 Mon Sep 17 00:00:00 2001 From: Abdullah Ahmed Date: Sat, 12 Oct 2019 16:00:04 -0400 Subject: refactor --- src/client/ClientRecommender.tsx | 74 +++++++++++----------- src/client/cognitive_services/CognitiveServices.ts | 15 ++--- src/client/views/nodes/DocumentView.tsx | 2 +- 3 files changed, 45 insertions(+), 46 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index bc1cd139c..0e1e8175a 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -36,7 +36,7 @@ export class ClientRecommender extends React.Component { private mainDoc?: RecommenderDocument; private docVectors: Set = new Set(); - @observable private corr_matrix = [[0, 0], [0, 0]]; + @observable private corr_matrix = [[0, 0], [0, 0]]; // for testing constructor(props: RecommenderProps) { //console.log("creating client recommender..."); @@ -90,16 +90,21 @@ export class ClientRecommender extends React.Component { * Returns list of {doc, similarity (to main doc)} in increasing score */ - public computeSimilarities() { + public computeSimilarities(distance_metric: string) { ClientRecommender.Instance.docVectors.forEach((doc: RecommenderDocument) => { if (ClientRecommender.Instance.mainDoc) { - const distance = ClientRecommender.Instance.distance(ClientRecommender.Instance.mainDoc.vectorDoc, doc.vectorDoc, "cosine"); + const distance = ClientRecommender.Instance.distance(ClientRecommender.Instance.mainDoc.vectorDoc, doc.vectorDoc, distance_metric); doc.score = distance; } } ); let doclist = Array.from(ClientRecommender.Instance.docVectors); - doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score); + if (distance_metric == "euclidian") { + doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score); + } + else { + doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => b.score - a.score); + } return doclist; } @@ -107,8 +112,8 @@ export class ClientRecommender extends React.Component { * Computes the mean of a set of vectors */ - public mean(paragraph: Set, dataDoc: Doc, mainDoc: boolean) { - const n = 200; + public mean(paragraph: Set) { + const n = 512; const num_words = paragraph.size; let meanVector = new Array(n).fill(0); // mean vector if (num_words > 0) { // check to see if paragraph actually was vectorized @@ -118,23 +123,20 @@ export class ClientRecommender extends React.Component { } }); meanVector = meanVector.map(x => x / num_words); - const internalDoc: RecommenderDocument = { actualDoc: dataDoc, vectorDoc: meanVector, score: 0 }; - if (mainDoc) ClientRecommender.Instance.mainDoc = internalDoc; - ClientRecommender.Instance.addToDocSet(internalDoc); } return meanVector; } - public processVector(vector: number[], dataDoc: Doc, mainDoc: boolean) { + public processVector(vector: number[], dataDoc: Doc, isMainDoc: boolean) { if (vector.length > 0) { const internalDoc: RecommenderDocument = { actualDoc: dataDoc, vectorDoc: vector, score: 0 }; - if (mainDoc) ClientRecommender.Instance.mainDoc = internalDoc; - ClientRecommender.Instance.addToDocSet(internalDoc); + ClientRecommender.Instance.addToDocSet(internalDoc, isMainDoc); } } - private addToDocSet(internalDoc: RecommenderDocument) { + private addToDocSet(internalDoc: RecommenderDocument, isMainDoc: boolean) { if (ClientRecommender.Instance.docVectors) { + if (isMainDoc) ClientRecommender.Instance.mainDoc = internalDoc; ClientRecommender.Instance.docVectors.add(internalDoc); } } @@ -143,22 +145,25 @@ export class ClientRecommender extends React.Component { * Uses Cognitive Services to extract keywords from a document */ - public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, mainDoc: boolean = false) { + public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, isMainDoc: boolean = false) { let fielddata = Cast(dataDoc.data, RichTextField); let data: string; fielddata ? data = fielddata[ToPlainText]() : data = ""; let converter = async (results: any, data: string) => { let keyterms = new List(); // raw keywords - let keyterms_counted = new List(); // keywords, where each keyword is repeated as - let kp_string: string = ""; + // let keyterms_counted = new List(); // keywords, where each keyword is repeated. input to w2v + let kp_string: string = ""; // keywords concatenated into a string. input into TF let highKP: string[] = [""]; // most frequent keyphrase let high = 0; results.documents.forEach((doc: any) => { let keyPhrases = doc.keyPhrases; keyPhrases.map((kp: string) => { - const frequency = this.countFrequencies(kp, data); keyterms.push(kp); - kp_string += kp + ", "; + const frequency = this.countFrequencies(kp, data); // frequency of keyphrase in paragraph + kp_string += kp + ", "; // ensures that if frequency is 0 for some reason kp is still added + for (let i = 0; i < frequency - 1; i++) { + kp_string += kp + ", "; // weights repeated keywords higher + } // replaces highKP with new one if (frequency > high) { high = frequency; @@ -168,24 +173,25 @@ export class ClientRecommender extends React.Component { else if (frequency === high) { highKP.push(kp); } - let words = kp.split(" "); // separates phrase into words - words = this.removeStopWords(words); // removes stop words if they appear in phrases - words.forEach((word) => { - //keyterms.push(word); - for (let i = 0; i < frequency; i++) { - keyterms_counted.push(word); - } - }); + // let words = kp.split(" "); // separates phrase into words + // words = this.removeStopWords(words); // removes stop words if they appear in phrases + // words.forEach((word) => { + // for (let i = 0; i < frequency; i++) { + // keyterms_counted.push(word); + // } + // }); }); }); - const kts_counted = new List(); - keyterms_counted.forEach(kt => kts_counted.push(kt.toLowerCase())); + // const kts_counted = new List(); + // keyterms_counted.forEach(kt => kts_counted.push(kt.toLowerCase())); + if (kp_string.length > 2) kp_string = kp_string.substring(0, kp_string.length - 2); + console.log("kp string: ", kp_string); let values = ""; if (!internal) values = await this.sendRequest(highKP); - return { keyterms: keyterms, keyterms_counted: kts_counted, values, kp_string: [kp_string] }; + return { keyterms: keyterms, external_recommendations: values, kp_string: [kp_string] }; }; if (data != "") { - return CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc, internal); + return CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, isMainDoc, internal); } return; } @@ -196,7 +202,7 @@ export class ClientRecommender extends React.Component { */ private countFrequencies(keyphrase: string, paragraph: string) { - let data = paragraph.split(" "); + let data = paragraph.split(/ |\n/); // splits by new lines and spaces let kp_array = keyphrase.split(" "); let num_keywords = kp_array.length; let par_length = data.length; @@ -289,12 +295,6 @@ export class ClientRecommender extends React.Component { return new Promise(promisified); } - processArxivResult = (result: any) => { - var xmlDoc = result as XMLDocument; - let text = xmlDoc.getElementsByTagName("title")[0].childNodes[0].nodeValue; - console.log(text); - } - render() { return (
); diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index eb088763d..48519f916 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -15,7 +15,7 @@ type RequestExecutor = (apiKey: string, body: string, service: Service) => Promi type AnalysisApplier = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any; type BodyConverter = (data: D) => string; type Converter = (results: any) => Field; -type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field, keyterms_counted: Field, values: any, kp_string: string[] }>; +type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field, external_recommendations: any, kp_string: string[] }>; export type Tag = { name: string, confidence: number }; export type Rectangle = { top: number, left: number, width: number, height: number }; @@ -264,7 +264,7 @@ export namespace CognitiveServices { export namespace Appliers { - export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false, data: string) { + export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) { console.log("vectorizing..."); //keyterms = ["father", "king"]; let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true }; @@ -287,17 +287,16 @@ export namespace CognitiveServices { ); } - export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, mainDoc: boolean = false, internal: boolean = true) => { + export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, isMainDoc: boolean = false, internal: boolean = true) => { let results = await ExecuteQuery(Service.Text, Manager, data); console.log(results); - let { keyterms, values, keyterms_counted, kp_string } = await converter(results, data); - //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis"); + let { keyterms, external_recommendations, kp_string } = await converter(results, data); target[keys[0]] = keyterms; - console.log("analyzed!"); if (internal) { - await vectorize(kp_string, dataDoc, mainDoc, data); + //await vectorize([data], dataDoc, isMainDoc); + await vectorize(kp_string, dataDoc, isMainDoc); } else { - return values; + return external_recommendations; } }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 070b1f426..ab2717eed 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -602,7 +602,7 @@ export class DocumentView extends DocComponent(Docu } } })); - const doclist = ClientRecommender.Instance.computeSimilarities(); + const doclist = ClientRecommender.Instance.computeSimilarities("cosine"); let recDocs: { preview: Doc, score: number }[] = []; // tslint:disable-next-line: prefer-for-of for (let i = 0; i < doclist.length; i++) { -- cgit v1.2.3-70-g09d2 From 5e04d7f94d180dc984e48eca888ac2cedfd0c7a3 Mon Sep 17 00:00:00 2001 From: ab Date: Thu, 17 Oct 2019 13:18:52 -0400 Subject: image stuff --- src/client/ClientRecommender.tsx | 9 ++++++--- src/client/views/nodes/DocumentView.tsx | 16 +++++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 0e1e8175a..73b05cf1a 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -145,14 +145,17 @@ export class ClientRecommender extends React.Component { * Uses Cognitive Services to extract keywords from a document */ - public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, isMainDoc: boolean = false) { + public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, isMainDoc: boolean = false, image: boolean = false) { let fielddata = Cast(dataDoc.data, RichTextField); + if (image && extDoc.generatedTags) { + console.log(StrCast(extDoc.generatedTags)); + } let data: string; fielddata ? data = fielddata[ToPlainText]() : data = ""; let converter = async (results: any, data: string) => { let keyterms = new List(); // raw keywords // let keyterms_counted = new List(); // keywords, where each keyword is repeated. input to w2v - let kp_string: string = ""; // keywords concatenated into a string. input into TF + let kp_string: string = ""; // keywords*frequency concatenated into a string. input into TF let highKP: string[] = [""]; // most frequent keyphrase let high = 0; results.documents.forEach((doc: any) => { @@ -190,7 +193,7 @@ export class ClientRecommender extends React.Component { if (!internal) values = await this.sendRequest(highKP); return { keyterms: keyterms, external_recommendations: values, kp_string: [kp_string] }; }; - if (data != "") { + if (data !== "") { return CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, isMainDoc, internal); } return; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ab2717eed..960ec744e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -589,16 +589,26 @@ export class DocumentView extends DocComponent(Docu ClientRecommender.Instance.reset_docs(); //ClientRecommender.Instance.arxivrequest("electrons"); await Promise.all(allDocs.map((doc: Doc) => { - let mainDoc: boolean = false; + let isMainDoc: boolean = false; const dataDoc = Doc.GetDataDoc(doc); if (doc.type === DocumentType.TEXT) { if (dataDoc === Doc.GetDataDoc(this.props.Document)) { - mainDoc = true; + isMainDoc = true; } if (!documents.includes(dataDoc)) { documents.push(dataDoc); const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, mainDoc); + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, isMainDoc); + } + } + if (doc.type === DocumentType.IMG) { + if (dataDoc === Doc.GetDataDoc(this.props.Document)) { + isMainDoc = true; + } + if (!documents.includes(dataDoc)) { + documents.push(dataDoc); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, isMainDoc, true); } } })); -- cgit v1.2.3-70-g09d2 From 301784883375860a7c693db97fe8f279e5380caf Mon Sep 17 00:00:00 2001 From: ab Date: Sat, 19 Oct 2019 16:51:45 -0400 Subject: image stuff --- src/client/ClientRecommender.tsx | 3 +- src/client/documents/Documents.ts | 2 +- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/MainView.tsx | 2 +- src/client/views/Recommendations.scss | 68 --------- src/client/views/Recommendations.tsx | 185 ------------------------ src/client/views/RecommendationsBox.scss | 68 +++++++++ src/client/views/RecommendationsBox.tsx | 185 ++++++++++++++++++++++++ src/client/views/nodes/DocumentContentsView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 2 +- 10 files changed, 261 insertions(+), 260 deletions(-) delete mode 100644 src/client/views/Recommendations.scss delete mode 100644 src/client/views/Recommendations.tsx create mode 100644 src/client/views/RecommendationsBox.scss create mode 100644 src/client/views/RecommendationsBox.tsx (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 73b05cf1a..97efedd89 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -12,6 +12,7 @@ import "./ClientRecommender.scss"; import { JSXElement } from "babel-types"; import { RichTextField } from "../new_fields/RichTextField"; import { ToPlainText } from "../new_fields/FieldSymbols"; +import { listSpec } from "../new_fields/Schema"; export interface RecommenderProps { title: string; @@ -148,7 +149,7 @@ export class ClientRecommender extends React.Component { public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, isMainDoc: boolean = false, image: boolean = false) { let fielddata = Cast(dataDoc.data, RichTextField); if (image && extDoc.generatedTags) { - console.log(StrCast(extDoc.generatedTags)); + console.log(Cast(extDoc.generatedTags, listSpec("string"))); } let data: string; fielddata ? data = fielddata[ToPlainText]() : data = ""; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1179f0238..36f20199c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -43,7 +43,7 @@ import { PresBox } from "../views/nodes/PresBox"; import { ComputedField, ScriptField } from "../../new_fields/ScriptField"; import { ProxyField } from "../../new_fields/Proxy"; import { DocumentType } from "./DocumentTypes"; -import { RecommendationsBox } from "../views/Recommendations"; +import { RecommendationsBox } from "../views/RecommendationsBox"; //import { PresBox } from "../views/nodes/PresBox"; //import { PresField } from "../../new_fields/PresField"; import { LinkFollowBox } from "../views/linking/LinkFollowBox"; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 189e2bbe0..b0d0a0d28 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -6,7 +6,7 @@ import { DragManager } from "../util/DragManager"; import { action, runInAction } from "mobx"; import { Doc } from "../../new_fields/Doc"; import { DictationManager } from "../util/DictationManager"; -import { RecommendationsBox } from "./Recommendations"; +import { RecommendationsBox } from "./RecommendationsBox"; import SharingManager from "../util/SharingManager"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import { Cast, PromiseValue } from "../../new_fields/Types"; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index bfd9c2163..2b47c2534 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -38,7 +38,7 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; //import { DocumentManager } from '../util/DocumentManager'; -import { RecommendationsBox } from './Recommendations'; +import { RecommendationsBox } from './RecommendationsBox'; import { PresBox } from './nodes/PresBox'; import { OverlayView } from './OverlayView'; diff --git a/src/client/views/Recommendations.scss b/src/client/views/Recommendations.scss deleted file mode 100644 index dd8a105f6..000000000 --- a/src/client/views/Recommendations.scss +++ /dev/null @@ -1,68 +0,0 @@ -@import "globalCssVariables"; - -.rec-content *{ - display: inline-block; - margin: auto; - width: 50; - height: 150px; - border: 1px dashed grey; - padding: 10px 10px; -} - -.rec-content { - float: left; - width: inherit; - align-content: center; -} - -.rec-scroll { - overflow-y: scroll; - overflow-x: hidden; - position: absolute; - pointer-events: all; - // display: flex; - z-index: 10000; - box-shadow: gray 0.2vw 0.2vw 0.4vw; - // flex-direction: column; - background: whitesmoke; - padding-bottom: 10px; - padding-top: 20px; - // border-radius: 15px; - border: solid #BBBBBBBB 1px; - width: 100%; - text-align: center; - // max-height: 250px; - height: 100%; - text-transform: uppercase; - color: grey; - letter-spacing: 2px; -} - -.content { - padding: 10px; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} - -.image-background { - pointer-events: none; - background-color: transparent; - width: 50%; - text-align: center; - margin-left: 5px; -} - -img{ - width: 100%; - height: 100%; -} - -.score { - // margin-left: 15px; - width: 50%; - height: 100%; - text-align: center; - margin-left: 10px; -} diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx deleted file mode 100644 index f965d655b..000000000 --- a/src/client/views/Recommendations.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import { observer } from "mobx-react"; -import React = require("react"); -import { observable, action } from "mobx"; -import Measure from "react-measure"; -import "./Recommendations.scss"; -import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc"; -import { DocumentIcon } from "./nodes/DocumentIcon"; -import { StrCast, NumCast } from "../../new_fields/Types"; -import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils"; -import { Transform } from "../util/Transform"; -import { ObjectField } from "../../new_fields/ObjectField"; -import { DocumentView } from "./nodes/DocumentView"; -import { DocumentType } from '../documents/DocumentTypes'; -import { ClientRecommender } from "../ClientRecommender"; -import { DocServer } from "../DocServer"; -import { Id } from "../../new_fields/FieldSymbols"; -import { FieldView, FieldViewProps } from "./nodes/FieldView"; -import { DocumentManager } from "../util/DocumentManager"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons"; -import { DocUtils } from "../documents/Documents"; - -export interface RecProps { - documents: { preview: Doc, similarity: number }[]; - node: Doc; -} - -library.add(faBullseye, faLink); - -@observer -export class RecommendationsBox extends React.Component { - - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(RecommendationsBox, fieldKey); } - - static Instance: RecommendationsBox; - // @observable private _display: boolean = false; - @observable private _pageX: number = 0; - @observable private _pageY: number = 0; - @observable private _width: number = 0; - @observable private _height: number = 0; - // @observable private _documents: { preview: Doc, score: number }[] = []; - private previewDocs: Doc[] = []; - - constructor(props: FieldViewProps) { - super(props); - RecommendationsBox.Instance = this; - } - - private DocumentIcon(doc: Doc) { - let layoutresult = StrCast(doc.type); - let renderDoc = doc; - //let box: number[] = []; - if (layoutresult.indexOf(DocumentType.COL) !== -1) { - renderDoc = Doc.MakeDelegate(renderDoc); - let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { - var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); - } - let returnXDimension = () => 150; - let returnYDimension = () => 150; - let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); - //let scale = () => 1; - let newRenderDoc = Doc.MakeAlias(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt - newRenderDoc.height = NumCast(this.props.Document.documentIconHeight); - newRenderDoc.autoHeight = false; - const docview =
- -
; - return docview; - - } - - // @action - // closeMenu = () => { - // this._display = false; - // this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id])); - // this.previewDocs = []; - // } - - // @action - // resetDocuments = () => { - // this._documents = []; - // } - - // @action - // displayRecommendations(x: number, y: number) { - // this._pageX = x; - // this._pageY = y; - // this._display = true; - // } - - static readonly buffer = 20; - - // get pageX() { - // const x = this._pageX; - // if (x < 0) { - // return 0; - // } - // const width = this._width; - // if (x + width > window.innerWidth - RecommendationsBox.buffer) { - // return window.innerWidth - RecommendationsBox.buffer - width; - // } - // return x; - // } - - // get pageY() { - // const y = this._pageY; - // if (y < 0) { - // return 0; - // } - // const height = this._height; - // if (y + height > window.innerHeight - RecommendationsBox.buffer) { - // return window.innerHeight - RecommendationsBox.buffer - height; - // } - // return y; - // } - - render() { - // if (!this._display) { - // return null; - // } - // let style = { left: this.pageX, top: this.pageY }; - //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" - let title = StrCast((this.props.Document.sourceDoc as Doc).title); - if (title.length > 15) { - title = title.substring(0, 15) + "..."; - } - return ( - // { this._width = r.offset.width; this._height = r.offset.height; })}> - // {({ measureRef }) => ( -
-

Recommendations for "{title}"

- {DocListCast(this.props.Document.data).map(doc => { - return ( -
- - {this.DocumentIcon(doc)} - - {NumCast(doc.score).toFixed(4)} -
DocumentManager.Instance.jumpToDocument(doc, false)}> - -
-
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> - -
-
- ); - })} - -
- // ); - // } - - //
- ); - } -} \ No newline at end of file diff --git a/src/client/views/RecommendationsBox.scss b/src/client/views/RecommendationsBox.scss new file mode 100644 index 000000000..dd8a105f6 --- /dev/null +++ b/src/client/views/RecommendationsBox.scss @@ -0,0 +1,68 @@ +@import "globalCssVariables"; + +.rec-content *{ + display: inline-block; + margin: auto; + width: 50; + height: 150px; + border: 1px dashed grey; + padding: 10px 10px; +} + +.rec-content { + float: left; + width: inherit; + align-content: center; +} + +.rec-scroll { + overflow-y: scroll; + overflow-x: hidden; + position: absolute; + pointer-events: all; + // display: flex; + z-index: 10000; + box-shadow: gray 0.2vw 0.2vw 0.4vw; + // flex-direction: column; + background: whitesmoke; + padding-bottom: 10px; + padding-top: 20px; + // border-radius: 15px; + border: solid #BBBBBBBB 1px; + width: 100%; + text-align: center; + // max-height: 250px; + height: 100%; + text-transform: uppercase; + color: grey; + letter-spacing: 2px; +} + +.content { + padding: 10px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.image-background { + pointer-events: none; + background-color: transparent; + width: 50%; + text-align: center; + margin-left: 5px; +} + +img{ + width: 100%; + height: 100%; +} + +.score { + // margin-left: 15px; + width: 50%; + height: 100%; + text-align: center; + margin-left: 10px; +} diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx new file mode 100644 index 000000000..3938a8690 --- /dev/null +++ b/src/client/views/RecommendationsBox.tsx @@ -0,0 +1,185 @@ +import { observer } from "mobx-react"; +import React = require("react"); +import { observable, action } from "mobx"; +import Measure from "react-measure"; +import "./RecommendationsBox.scss"; +import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc"; +import { DocumentIcon } from "./nodes/DocumentIcon"; +import { StrCast, NumCast } from "../../new_fields/Types"; +import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils"; +import { Transform } from "../util/Transform"; +import { ObjectField } from "../../new_fields/ObjectField"; +import { DocumentView } from "./nodes/DocumentView"; +import { DocumentType } from '../documents/DocumentTypes'; +import { ClientRecommender } from "../ClientRecommender"; +import { DocServer } from "../DocServer"; +import { Id } from "../../new_fields/FieldSymbols"; +import { FieldView, FieldViewProps } from "./nodes/FieldView"; +import { DocumentManager } from "../util/DocumentManager"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons"; +import { DocUtils } from "../documents/Documents"; + +export interface RecProps { + documents: { preview: Doc, similarity: number }[]; + node: Doc; +} + +library.add(faBullseye, faLink); + +@observer +export class RecommendationsBox extends React.Component { + + public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(RecommendationsBox, fieldKey); } + + static Instance: RecommendationsBox; + // @observable private _display: boolean = false; + @observable private _pageX: number = 0; + @observable private _pageY: number = 0; + @observable private _width: number = 0; + @observable private _height: number = 0; + // @observable private _documents: { preview: Doc, score: number }[] = []; + private previewDocs: Doc[] = []; + + constructor(props: FieldViewProps) { + super(props); + RecommendationsBox.Instance = this; + } + + private DocumentIcon(doc: Doc) { + let layoutresult = StrCast(doc.type); + let renderDoc = doc; + //let box: number[] = []; + if (layoutresult.indexOf(DocumentType.COL) !== -1) { + renderDoc = Doc.MakeDelegate(renderDoc); + let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { + var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; + let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; + return { + x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) + }; + }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); + } + let returnXDimension = () => 150; + let returnYDimension = () => 150; + let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); + //let scale = () => 1; + let newRenderDoc = Doc.MakeAlias(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt + newRenderDoc.height = NumCast(this.props.Document.documentIconHeight); + newRenderDoc.autoHeight = false; + const docview =
+ +
; + return docview; + + } + + // @action + // closeMenu = () => { + // this._display = false; + // this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id])); + // this.previewDocs = []; + // } + + // @action + // resetDocuments = () => { + // this._documents = []; + // } + + // @action + // displayRecommendations(x: number, y: number) { + // this._pageX = x; + // this._pageY = y; + // this._display = true; + // } + + static readonly buffer = 20; + + // get pageX() { + // const x = this._pageX; + // if (x < 0) { + // return 0; + // } + // const width = this._width; + // if (x + width > window.innerWidth - RecommendationsBox.buffer) { + // return window.innerWidth - RecommendationsBox.buffer - width; + // } + // return x; + // } + + // get pageY() { + // const y = this._pageY; + // if (y < 0) { + // return 0; + // } + // const height = this._height; + // if (y + height > window.innerHeight - RecommendationsBox.buffer) { + // return window.innerHeight - RecommendationsBox.buffer - height; + // } + // return y; + // } + + render() { + // if (!this._display) { + // return null; + // } + // let style = { left: this.pageX, top: this.pageY }; + //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" + let title = StrCast((this.props.Document.sourceDoc as Doc).title); + if (title.length > 15) { + title = title.substring(0, 15) + "..."; + } + return ( + // { this._width = r.offset.width; this._height = r.offset.height; })}> + // {({ measureRef }) => ( +
+

Recommendations for "{title}"

+ {DocListCast(this.props.Document.data).map(doc => { + return ( +
+ + {this.DocumentIcon(doc)} + + {NumCast(doc.score).toFixed(4)} +
DocumentManager.Instance.jumpToDocument(doc, false)}> + +
+
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> + +
+
+ ); + })} + +
+ // ); + // } + + //
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 32d74b193..01096e5e5 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -31,7 +31,7 @@ import { WebBox } from "./WebBox"; import React = require("react"); import { Without, OmitKeys } from "../../../Utils"; import { Cast } from "../../../new_fields/Types"; -import { RecommendationsBox } from "../../views/Recommendations"; +import { RecommendationsBox } from "../RecommendationsBox"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? type BindingProps = Without; @@ -96,7 +96,7 @@ export class DocumentContentsView extends React.Component Date: Fri, 25 Oct 2019 16:21:45 -0400 Subject: images show up, react errors --- src/client/ClientRecommender.tsx | 78 ++++++++++++---------- src/client/cognitive_services/CognitiveServices.ts | 5 +- src/client/views/RecommendationsBox.tsx | 2 +- 3 files changed, 47 insertions(+), 38 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 97efedd89..ae2413d1d 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -1,4 +1,4 @@ -import { Doc } from "../new_fields/Doc"; +import { Doc, FieldResult } from "../new_fields/Doc"; import { StrCast, Cast } from "../new_fields/Types"; import { List } from "../new_fields/List"; import { CognitiveServices } from "./cognitive_services/CognitiveServices"; @@ -147,47 +147,55 @@ export class ClientRecommender extends React.Component { */ public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, isMainDoc: boolean = false, image: boolean = false) { - let fielddata = Cast(dataDoc.data, RichTextField); - if (image && extDoc.generatedTags) { - console.log(Cast(extDoc.generatedTags, listSpec("string"))); + let data: string = ""; + let taglist: FieldResult> = undefined; + if (image && extDoc.generatedTags) { // TODO: Automatically generate tags. Need to ask Sam about this. + taglist = Cast(extDoc.generatedTags, listSpec("string")); + taglist!.forEach(tag => { + data += tag + ", "; + }); + } + else { + let fielddata = Cast(dataDoc.data, RichTextField); + fielddata ? data = fielddata[ToPlainText]() : data = ""; } - let data: string; - fielddata ? data = fielddata[ToPlainText]() : data = ""; - let converter = async (results: any, data: string) => { + + let converter = async (results: any, data: string, isImage: boolean = false) => { let keyterms = new List(); // raw keywords // let keyterms_counted = new List(); // keywords, where each keyword is repeated. input to w2v let kp_string: string = ""; // keywords*frequency concatenated into a string. input into TF let highKP: string[] = [""]; // most frequent keyphrase let high = 0; - results.documents.forEach((doc: any) => { - let keyPhrases = doc.keyPhrases; - keyPhrases.map((kp: string) => { - keyterms.push(kp); - const frequency = this.countFrequencies(kp, data); // frequency of keyphrase in paragraph - kp_string += kp + ", "; // ensures that if frequency is 0 for some reason kp is still added - for (let i = 0; i < frequency - 1; i++) { - kp_string += kp + ", "; // weights repeated keywords higher - } - // replaces highKP with new one - if (frequency > high) { - high = frequency; - highKP = [kp]; - } - // appends to current highKP phrase - else if (frequency === high) { - highKP.push(kp); - } - // let words = kp.split(" "); // separates phrase into words - // words = this.removeStopWords(words); // removes stop words if they appear in phrases - // words.forEach((word) => { - // for (let i = 0; i < frequency; i++) { - // keyterms_counted.push(word); - // } - // }); + + if (isImage) { + kp_string = data; + if (taglist) { + keyterms = taglist; + highKP = [taglist[0]]; + } + } + else { // text processing + results.documents.forEach((doc: any) => { + let keyPhrases = doc.keyPhrases; + keyPhrases.map((kp: string) => { + keyterms.push(kp); + const frequency = this.countFrequencies(kp, data); // frequency of keyphrase in paragraph + kp_string += kp + ", "; // ensures that if frequency is 0 for some reason kp is still added + for (let i = 0; i < frequency - 1; i++) { + kp_string += kp + ", "; // weights repeated keywords higher + } + // replaces highKP with new one + if (frequency > high) { + high = frequency; + highKP = [kp]; + } + // appends to current highKP phrase + else if (frequency === high) { + highKP.push(kp); + } + }); }); - }); - // const kts_counted = new List(); - // keyterms_counted.forEach(kt => kts_counted.push(kt.toLowerCase())); + } if (kp_string.length > 2) kp_string = kp_string.substring(0, kp_string.length - 2); console.log("kp string: ", kp_string); let values = ""; diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index b0e9138a4..d496b442e 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -9,6 +9,7 @@ import { UndoManager } from "../util/UndoManager"; import requestPromise = require("request-promise"); import { List } from "../../new_fields/List"; import { ClientRecommender } from "../ClientRecommender"; +import { ImageBox } from "../views/nodes/ImageBox"; type APIManager = { converter: BodyConverter, requester: RequestExecutor }; type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise; @@ -288,12 +289,12 @@ export namespace CognitiveServices { ); } - export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, isMainDoc: boolean = false, internal: boolean = true) => { + export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, isMainDoc: boolean = false, isInternal: boolean = true) => { let results = await ExecuteQuery(Service.Text, Manager, data); console.log(results); let { keyterms, external_recommendations, kp_string } = await converter(results, data); target[keys[0]] = keyterms; - if (internal) { + if (isInternal) { //await vectorize([data], dataDoc, isMainDoc); await vectorize(kp_string, dataDoc, isMainDoc); } else { diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx index 3938a8690..c50550bef 100644 --- a/src/client/views/RecommendationsBox.tsx +++ b/src/client/views/RecommendationsBox.tsx @@ -143,7 +143,7 @@ export class RecommendationsBox extends React.Component { // return y; // } - render() { + render() { //TODO: Invariant violation: max depth exceeded error. Occurs when images are rendered. // if (!this._display) { // return null; // } -- cgit v1.2.3-70-g09d2 From 96dbd7d4fd1759ad1a5135ca94d46970ca31168f Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 30 Oct 2019 18:26:11 -0400 Subject: ui issues, max depth --- package.json | 5 ++- src/client/views/RecommendationsBox.tsx | 70 +++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 28 deletions(-) (limited to 'src/client/views') diff --git a/package.json b/package.json index b3cf9102b..fde685eba 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,9 @@ "@hig/theme-context": "^2.1.3", "@hig/theme-data": "^2.3.3", "@tensorflow-models/universal-sentence-encoder": "^1.2.0", - "@tensorflow/tfjs": "^1.2.9", - "@tensorflow/tfjs-node": "^1.2.11", + "@tensorflow/tfjs": "^1.3.1", + "@tensorflow/tfjs-core": "^1.3.1", + "@tensorflow/tfjs-node": "^1.3.1", "@trendmicro/react-dropdown": "^1.3.0", "@types/adm-zip": "^0.4.32", "@types/animejs": "^2.0.2", diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx index c50550bef..fd128bfca 100644 --- a/src/client/views/RecommendationsBox.tsx +++ b/src/client/views/RecommendationsBox.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react"; import React = require("react"); -import { observable, action } from "mobx"; +import { observable, action, computed } from "mobx"; import Measure from "react-measure"; import "./RecommendationsBox.scss"; import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc"; @@ -33,18 +33,17 @@ export class RecommendationsBox extends React.Component { public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(RecommendationsBox, fieldKey); } - static Instance: RecommendationsBox; // @observable private _display: boolean = false; @observable private _pageX: number = 0; @observable private _pageY: number = 0; @observable private _width: number = 0; @observable private _height: number = 0; + private _docViews: JSX.Element[] = []; // @observable private _documents: { preview: Doc, score: number }[] = []; private previewDocs: Doc[] = []; constructor(props: FieldViewProps) { super(props); - RecommendationsBox.Instance = this; } private DocumentIcon(doc: Doc) { @@ -143,6 +142,45 @@ export class RecommendationsBox extends React.Component { // return y; // } + get createDocViews() { + return DocListCast(this.props.Document.data).map(doc => { + return ( +
+ + {this.DocumentIcon(doc)} + + {NumCast(doc.score).toFixed(4)} +
DocumentManager.Instance.jumpToDocument(doc, false)}> + +
+
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> + +
+
+ ); + }); + } + + // componentDidMount() { + // this._docViews = DocListCast(this.props.Document.data).map(doc => { + // return ( + //
+ // + // {this.DocumentIcon(doc)} + // + // {NumCast(doc.score).toFixed(4)} + //
DocumentManager.Instance.jumpToDocument(doc, false)}> + // + //
+ //
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> + // + //
+ //
+ // ); + // }) + + // } + render() { //TODO: Invariant violation: max depth exceeded error. Occurs when images are rendered. // if (!this._display) { // return null; @@ -154,32 +192,12 @@ export class RecommendationsBox extends React.Component { title = title.substring(0, 15) + "..."; } return ( - // { this._width = r.offset.width; this._height = r.offset.height; })}> - // {({ measureRef }) => (

Recommendations for "{title}"

- {DocListCast(this.props.Document.data).map(doc => { - return ( -
- - {this.DocumentIcon(doc)} - - {NumCast(doc.score).toFixed(4)} -
DocumentManager.Instance.jumpToDocument(doc, false)}> - -
-
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> - -
-
- ); - })} - + {this.createDocViews}
- // ); - // } - - //
); } + // + // } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 3d5e2bf61e04fa700eccac10d19ea6dde436f6bd Mon Sep 17 00:00:00 2001 From: monikahedman Date: Thu, 31 Oct 2019 11:57:08 -0400 Subject: fixed depth problem --- src/client/ClientRecommender.tsx | 63 ++++++++++++++++++++++++++------ src/client/views/RecommendationsBox.tsx | 64 ++++++++++++++++----------------- src/client/views/nodes/ImageBox.tsx | 1 + 3 files changed, 83 insertions(+), 45 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 90dd240b6..364ec0fe0 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -1,7 +1,7 @@ import { Doc, FieldResult } from "../new_fields/Doc"; import { StrCast, Cast } from "../new_fields/Types"; import { List } from "../new_fields/List"; -import { CognitiveServices } from "./cognitive_services/CognitiveServices"; +import { CognitiveServices, Confidence, Tag, Service } from "./cognitive_services/CognitiveServices"; import React = require("react"); import { observer } from "mobx-react"; import { observable, action, computed, reaction } from "mobx"; @@ -14,6 +14,8 @@ import { RichTextField } from "../new_fields/RichTextField"; import { ToPlainText } from "../new_fields/FieldSymbols"; import { listSpec } from "../new_fields/Schema"; import { Identified } from "./Network"; +import { ComputedField } from "../new_fields/ScriptField"; +import { ImageField } from "../new_fields/URLField"; export interface RecommenderProps { title: string; @@ -31,9 +33,13 @@ export interface RecommenderDocument { score: number; } +const fieldkey = "data"; + @observer export class ClientRecommender extends React.Component { + + static Instance: ClientRecommender; private mainDoc?: RecommenderDocument; private docVectors: Set = new Set(); @@ -45,10 +51,6 @@ export class ClientRecommender extends React.Component { super(props); if (!ClientRecommender.Instance) ClientRecommender.Instance = this; ClientRecommender.Instance.docVectors = new Set(); - const parameters: any = {}; - Identified.PostToServer("/IBMAnalysis", parameters).then(response => { - console.log("ANALYSIS RESULTS! ", response); - }); //ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]]; } @@ -97,6 +99,10 @@ export class ClientRecommender extends React.Component { */ public computeSimilarities(distance_metric: string) { + const parameters: any = {}; + Identified.PostToServer("/IBMAnalysis", parameters).then(response => { + console.log("ANALYSIS RESULTS! ", response); + }); ClientRecommender.Instance.docVectors.forEach((doc: RecommenderDocument) => { if (ClientRecommender.Instance.mainDoc) { const distance = ClientRecommender.Instance.distance(ClientRecommender.Instance.mainDoc.vectorDoc, doc.vectorDoc, distance_metric); @@ -147,6 +153,39 @@ export class ClientRecommender extends React.Component { } } + /*** + * Generates tags for an image using Cognitive Services + */ + + generateMetadata = async (dataDoc: Doc, extDoc: Doc, threshold: Confidence = Confidence.Excellent) => { + let converter = (results: any) => { + let tagDoc = new Doc; + let tagsList = new List(); + results.tags.map((tag: Tag) => { + tagsList.push(tag.name); + let sanitized = tag.name.replace(" ", "_"); + tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`); + }); + extDoc.generatedTags = tagsList; + tagDoc.title = "Generated Tags Doc"; + tagDoc.confidence = threshold; + return tagDoc; + }; + const url = this.url(dataDoc); + if (url) { + return CognitiveServices.Image.Appliers.ProcessImage(extDoc, ["generatedTagsDoc"], url, Service.ComputerVision, converter); + } + } + + /*** + * Gets URL of image + */ + + private url(dataDoc: Doc) { + let data = Cast(Doc.GetProto(dataDoc)[fieldkey], ImageField); + return data ? data.url.href : undefined; + } + /*** * Uses Cognitive Services to extract keywords from a document */ @@ -154,17 +193,19 @@ export class ClientRecommender extends React.Component { public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, isMainDoc: boolean = false, image: boolean = false) { let data: string = ""; let taglist: FieldResult> = undefined; - if (image && extDoc.generatedTags) { // TODO: Automatically generate tags. Need to ask Sam about this. - taglist = Cast(extDoc.generatedTags, listSpec("string")); - taglist!.forEach(tag => { - data += tag + ", "; - }); + if (image) { + if (!extDoc.generatedTags) await this.generateMetadata(dataDoc, extDoc); // TODO: Automatically generate tags. Need to ask Sam about this. + if (extDoc.generatedTags) { + taglist = Cast(extDoc.generatedTags, listSpec("string")); + taglist!.forEach(tag => { + data += tag + ", "; + }); + } } else { let fielddata = Cast(dataDoc.data, RichTextField); fielddata ? data = fielddata[ToPlainText]() : data = ""; } - let converter = async (results: any, data: string, isImage: boolean = false) => { let keyterms = new List(); // raw keywords // let keyterms_counted = new List(); // keywords, where each keyword is repeated. input to w2v diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx index fd128bfca..0e3cfd729 100644 --- a/src/client/views/RecommendationsBox.tsx +++ b/src/client/views/RecommendationsBox.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react"; import React = require("react"); -import { observable, action, computed } from "mobx"; +import { observable, action, computed, runInAction } from "mobx"; import Measure from "react-measure"; import "./RecommendationsBox.scss"; import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc"; @@ -38,7 +38,7 @@ export class RecommendationsBox extends React.Component { @observable private _pageY: number = 0; @observable private _width: number = 0; @observable private _height: number = 0; - private _docViews: JSX.Element[] = []; + @observable.shallow private _docViews: JSX.Element[] = []; // @observable private _documents: { preview: Doc, score: number }[] = []; private previewDocs: Doc[] = []; @@ -46,20 +46,13 @@ export class RecommendationsBox extends React.Component { super(props); } + @action private DocumentIcon(doc: Doc) { let layoutresult = StrCast(doc.type); let renderDoc = doc; //let box: number[] = []; if (layoutresult.indexOf(DocumentType.COL) !== -1) { renderDoc = Doc.MakeDelegate(renderDoc); - let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { - var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); } let returnXDimension = () => 150; let returnYDimension = () => 150; @@ -142,27 +135,8 @@ export class RecommendationsBox extends React.Component { // return y; // } - get createDocViews() { - return DocListCast(this.props.Document.data).map(doc => { - return ( -
- - {this.DocumentIcon(doc)} - - {NumCast(doc.score).toFixed(4)} -
DocumentManager.Instance.jumpToDocument(doc, false)}> - -
-
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> - -
-
- ); - }); - } - - // componentDidMount() { - // this._docViews = DocListCast(this.props.Document.data).map(doc => { + // get createDocViews() { + // return DocListCast(this.props.Document.data).map(doc => { // return ( //
// @@ -177,10 +151,32 @@ export class RecommendationsBox extends React.Component { //
//
// ); - // }) - + // }); // } + componentDidMount() { //TODO: invoking a computedFn from outside an reactive context won't be memoized, unless keepAlive is set + runInAction(() => { + if (this._docViews.length === 0) { + this._docViews = DocListCast(this.props.Document.data).map(doc => { + return ( +
+ + {this.DocumentIcon(doc)} + + {NumCast(doc.score).toFixed(4)} +
DocumentManager.Instance.jumpToDocument(doc, false)}> + +
+
DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}> + +
+
+ ); + }); + } + }); + } + render() { //TODO: Invariant violation: max depth exceeded error. Occurs when images are rendered. // if (!this._display) { // return null; @@ -194,7 +190,7 @@ export class RecommendationsBox extends React.Component { return (

Recommendations for "{title}"

- {this.createDocViews} + {this._docViews}
); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 7816c0fe8..1bf2724c3 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -248,6 +248,7 @@ export class ImageBox extends DocAnnotatableComponent Date: Sat, 16 Nov 2019 16:45:41 -0500 Subject: ibm integrated, bing search -> server next time --- package.json | 7 ++- src/client/ClientRecommender.tsx | 66 ++++++++++++++++++++-- src/client/apis/IBM_Recommender.ts | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 -- src/client/views/nodes/DocumentView.tsx | 2 +- src/server/index.ts | 2 +- 6 files changed, 70 insertions(+), 18 deletions(-) (limited to 'src/client/views') diff --git a/package.json b/package.json index 80b65cc68..d41119a47 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,9 @@ "@hig/theme-context": "^2.1.3", "@hig/theme-data": "^2.3.3", "@tensorflow-models/universal-sentence-encoder": "^1.2.0", - "@tensorflow/tfjs": "^1.3.1", + "@tensorflow/tfjs-converter": "^1.3.2", "@tensorflow/tfjs-core": "^1.3.1", - "@tensorflow/tfjs-node": "^1.3.1", + "@tensorflow/tfjs-node": "1.3.1", "@trendmicro/react-dropdown": "^1.3.0", "@types/adm-zip": "^0.4.32", "@types/animejs": "^2.0.2", @@ -238,8 +238,9 @@ "url-loader": "^1.1.2", "uuid": "^3.3.2", "wikijs": "^6.0.1", + "word2vec": "^1.1.4", "words-to-numbers": "^1.5.1", "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index 364ec0fe0..a37434c0d 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -8,6 +8,7 @@ import { observable, action, computed, reaction } from "mobx"; var assert = require('assert'); var sw = require('stopword'); var FeedParser = require('feedparser'); +var https = require('https'); import "./ClientRecommender.scss"; import { JSXElement } from "babel-types"; import { RichTextField } from "../new_fields/RichTextField"; @@ -111,7 +112,7 @@ export class ClientRecommender extends React.Component { } ); let doclist = Array.from(ClientRecommender.Instance.docVectors); - if (distance_metric == "euclidian") { + if (distance_metric === "euclidian") { doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score); } else { @@ -245,7 +246,27 @@ export class ClientRecommender extends React.Component { if (kp_string.length > 2) kp_string = kp_string.substring(0, kp_string.length - 2); console.log("kp string: ", kp_string); let values = ""; - if (!internal) values = await this.sendRequest(highKP); + if (!internal) { + const parameters: any = { + 'language': 'en', + 'text': data, + 'features': { + 'keywords': { + 'sentiment': true, + 'emotion': true, + 'limit': 3 + } + } + }; + await Identified.PostToServer("/IBMAnalysis", parameters).then(response => { + const sorted_keywords = response.result.keywords; + if (sorted_keywords.length > 0) { + console.log("IBM keyphrase", sorted_keywords[0]); + highKP = [sorted_keywords[0].text]; + } + }); + values = await this.sendRequest(highKP, "bing"); + } return { keyterms: keyterms, external_recommendations: values, kp_string: [kp_string] }; }; if (data !== "") { @@ -290,11 +311,46 @@ export class ClientRecommender extends React.Component { * API for sending arXiv request. */ - private async sendRequest(keywords: string[]) { + private async sendRequest(keywords: string[], api: string) { let query = ""; keywords.forEach((kp: string) => query += " " + kp); - return new Promise(resolve => { - this.arxivrequest(query).then(resolve); + if (api === "arxiv") { + return new Promise(resolve => { + this.arxivrequest(query).then(resolve); + }); + } + else if (api === "bing") { + await this.bingWebSearch(query); + } + else { + return new Promise(resolve => { + this.arxivrequest(query).then(resolve); + }); + } + + } + + bingWebSearch = async (query: string) => { + https.get({ + hostname: 'api.cognitive.microsoft.com', + path: '/bing/v5.0/search?q=' + encodeURIComponent(query), + headers: { 'Ocp-Apim-Subscription-Key': process.env.BING }, + }, (res: any) => { + let body = ''; + res.on('data', (part: any) => body += part); + res.on('end', () => { + for (var header in res.headers) { + if (header.startsWith("bingapis-") || header.startsWith("x-msedge-")) { + console.log(header + ": " + res.headers[header]) + } + } + console.log('\nJSON Response:\n'); + console.dir(JSON.parse(body), { colors: false, depth: null }); + }) + res.on('error', (e: any) => { + console.log('Error: ' + e.message); + throw e; + }); }); } diff --git a/src/client/apis/IBM_Recommender.ts b/src/client/apis/IBM_Recommender.ts index 5f80f5126..da6257f28 100644 --- a/src/client/apis/IBM_Recommender.ts +++ b/src/client/apis/IBM_Recommender.ts @@ -16,19 +16,19 @@ export namespace IBM_Recommender { }); const analyzeParams = { - 'url': 'www.ibm.com', + 'text': 'this is a test of the keyword extraction feature I am integrating into the program', 'features': { 'keywords': { 'sentiment': true, 'emotion': true, 'limit': 3 - } + }, } }; export const analyze = async (_parameters: any): Promise> => { try { - const response = await naturalLanguageUnderstanding.analyze(analyzeParams); + const response = await naturalLanguageUnderstanding.analyze(_parameters); console.log(response); return (JSON.stringify(response, null, 2)); } catch (err) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index bb5d99a28..bfec545c6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -34,11 +34,6 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import v5 = require("uuid/v5"); -import { ClientRecommender } from "../../../ClientRecommender"; -import { SearchUtil } from "../../../util/SearchUtil"; -import { RouteStore } from "../../../../server/RouteStore"; -import { string, number, elementType } from "prop-types"; import { DocServer } from "../../../DocServer"; import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; import { DocumentViewProps } from "../../nodes/DocumentView"; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 55063a52c..c6ad2f9d7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -772,7 +772,7 @@ export class DocumentView extends DocComponent(Docu } render() { if (!this.props.Document) return (null); - trace(); + //trace(); const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined; const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined; const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; diff --git a/src/server/index.ts b/src/server/index.ts index 2a669f147..45fc7fc07 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1086,7 +1086,7 @@ addSecureRoute({ const batched = BatchedArray.from(media, { batchSize: 25 }); const newMediaItems = await batched.batchedMapPatientInterval( { magnitude: 100, unit: TimeUnit.Milliseconds }, - async (batch, collector) => { + async (batch: any, collector: any): Promise => { for (let index = 0; index < batch.length; index++) { const { url, description } = batch[index]; const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(url); -- cgit v1.2.3-70-g09d2 From c3720ee2e2828d112b6ad34a7b6106d3c5f27878 Mon Sep 17 00:00:00 2001 From: ab Date: Wed, 20 Nov 2019 16:28:39 -0500 Subject: bing integrated --- src/client/ClientRecommender.tsx | 41 ++++++---------- src/client/cognitive_services/CognitiveServices.ts | 54 +++++++++++++++++++++- src/client/views/nodes/DocumentView.tsx | 23 +++++++-- src/server/index.ts | 3 +- 4 files changed, 88 insertions(+), 33 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index a37434c0d..83ca48590 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -191,7 +191,7 @@ export class ClientRecommender extends React.Component { * Uses Cognitive Services to extract keywords from a document */ - public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, isMainDoc: boolean = false, image: boolean = false) { + public async extractText(dataDoc: Doc, extDoc: Doc, internal: boolean = true, api: string = "bing", isMainDoc: boolean = false, image: boolean = false) { let data: string = ""; let taglist: FieldResult> = undefined; if (image) { @@ -265,7 +265,7 @@ export class ClientRecommender extends React.Component { highKP = [sorted_keywords[0].text]; } }); - values = await this.sendRequest(highKP, "bing"); + values = await this.sendRequest(highKP, api); } return { keyterms: keyterms, external_recommendations: values, kp_string: [kp_string] }; }; @@ -320,38 +320,27 @@ export class ClientRecommender extends React.Component { }); } else if (api === "bing") { - await this.bingWebSearch(query); - } - else { return new Promise(resolve => { - this.arxivrequest(query).then(resolve); + this.bingWebSearch(query).then(resolve); }); } + else { + console.log("no api specified :("); + } } bingWebSearch = async (query: string) => { - https.get({ - hostname: 'api.cognitive.microsoft.com', - path: '/bing/v5.0/search?q=' + encodeURIComponent(query), - headers: { 'Ocp-Apim-Subscription-Key': process.env.BING }, - }, (res: any) => { - let body = ''; - res.on('data', (part: any) => body += part); - res.on('end', () => { - for (var header in res.headers) { - if (header.startsWith("bingapis-") || header.startsWith("x-msedge-")) { - console.log(header + ": " + res.headers[header]) - } - } - console.log('\nJSON Response:\n'); - console.dir(JSON.parse(body), { colors: false, depth: null }); - }) - res.on('error', (e: any) => { - console.log('Error: ' + e.message); - throw e; + const converter = async (results: any) => { + let title_vals: string[] = []; + let url_vals: string[] = []; + results.webPages.value.forEach((doc: any) => { + title_vals.push(doc.name); + url_vals.push(doc.url); }); - }); + return { title_vals, url_vals }; + }; + return CognitiveServices.BingSearch.Appliers.analyzer(query, converter); } /** diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index d496b442e..641747207 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -17,6 +17,7 @@ type AnalysisApplier = (target: Doc, relevantKeys: string[], data: D, ...args type BodyConverter = (data: D) => string; type Converter = (results: any) => Field; type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field, external_recommendations: any, kp_string: string[] }>; +type BingConverter = (results: any) => Promise<{ title_vals: string[], url_vals: string[] }>; export type Tag = { name: string, confidence: number }; export type Rectangle = { top: number, left: number, width: number, height: number }; @@ -25,7 +26,8 @@ export enum Service { ComputerVision = "vision", Face = "face", Handwriting = "handwriting", - Text = "text" + Text = "text", + Bing = "bing" } export enum Confidence { @@ -225,6 +227,56 @@ export namespace CognitiveServices { } + export namespace BingSearch { + export const Manager: APIManager = { + converter: (data: string) => { + return data; + }, + requester: async (apiKey: string, query: string) => { + let xhttp = new XMLHttpRequest(); + let serverAddress = "https://api.cognitive.microsoft.com"; + let endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query); + let promisified = (resolve: any, reject: any) => { + xhttp.onreadystatechange = function () { + if (this.readyState === 4) { + let result = xhttp.responseText; + switch (this.status) { + case 200: + return resolve(result); + case 400: + default: + return reject(result); + } + } + }; + + if (apiKey) { + xhttp.open("GET", endpoint, true); + xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey); + xhttp.setRequestHeader('Content-Type', 'application/json'); + xhttp.send(); + } + else { + console.log("API key for BING unavailable"); + } + }; + return new Promise(promisified); + } + + }; + + export namespace Appliers { + export const analyzer = async (query: string, converter: BingConverter) => { + let results = await ExecuteQuery(Service.Bing, Manager, query); + console.log(results); + const { title_vals, url_vals } = await converter(results); + return { title_vals, url_vals }; + }; + } + + } + + export namespace Text { export const Manager: APIManager = { converter: (data: string) => { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c6ad2f9d7..2455c320d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -518,9 +518,22 @@ export class DocumentView extends DocComponent(Docu icon: "brain" }); + let ext_recommender_subitems: ContextMenuProps[] = []; + + ext_recommender_subitems.push({ + description: "arXiv", + event: () => this.externalRecommendation(e, "arxiv"), + icon: "brain" + }); + ext_recommender_subitems.push({ + description: "Bing", + event: () => this.externalRecommendation(e, "bing"), + icon: "brain" + }); + recommender_subitems.push({ description: "External recommendations", - event: () => this.externalRecommendation(e), + subitems: ext_recommender_subitems, icon: "brain" }); @@ -590,7 +603,7 @@ export class DocumentView extends DocComponent(Docu if (!documents.includes(dataDoc)) { documents.push(dataDoc); const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, isMainDoc); + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc); } } if (doc.type === DocumentType.IMG) { @@ -600,7 +613,7 @@ export class DocumentView extends DocComponent(Docu if (!documents.includes(dataDoc)) { documents.push(dataDoc); const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, isMainDoc, true); + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc, true); } } })); @@ -628,12 +641,12 @@ export class DocumentView extends DocComponent(Docu // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY); } - externalRecommendation = async (e: React.MouseEvent) => { + externalRecommendation = async (e: React.MouseEvent, api: string) => { if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); ClientRecommender.Instance.reset_docs(); const doc = Doc.GetDataDoc(this.props.Document); const extdoc = doc.data_ext as Doc; - const values = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false); + const values = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false, api); const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")]; let bodies: Doc[] = []; const titles = values.title_vals; diff --git a/src/server/index.ts b/src/server/index.ts index 45fc7fc07..050ab8755 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -674,7 +674,8 @@ const ServicesApiKeyMap = new Map([ ["face", process.env.FACE], ["vision", process.env.VISION], ["handwriting", process.env.HANDWRITING], - ["text", process.env.TEXT] + ["text", process.env.TEXT], + ["bing", process.env.BING] ]); addSecureRoute({ -- cgit v1.2.3-70-g09d2 From a231b597d70f8ee9e435a86b00020c93691dc97d Mon Sep 17 00:00:00 2001 From: ab Date: Sat, 23 Nov 2019 16:06:30 -0500 Subject: keyphrase query ui --- src/client/ClientRecommender.tsx | 2 + src/client/cognitive_services/CognitiveServices.ts | 49 ++++++++++++++++++++++ src/client/views/KeyphraseQueryView.scss | 8 ++++ src/client/views/KeyphraseQueryView.tsx | 30 +++++++++++++ .../views/collections/CollectionViewChromes.scss | 2 +- src/client/views/nodes/DocumentView.tsx | 38 ++++++++++------- 6 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 src/client/views/KeyphraseQueryView.scss create mode 100644 src/client/views/KeyphraseQueryView.tsx (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index d2e2330b5..b384a8ebe 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -17,6 +17,7 @@ import { listSpec } from "../new_fields/Schema"; import { Identified } from "./Network"; import { ComputedField } from "../new_fields/ScriptField"; import { ImageField } from "../new_fields/URLField"; +import { KeyphraseQueryView } from "./views/KeyphraseQueryView"; export interface RecommenderProps { title: string; @@ -279,6 +280,7 @@ export class ClientRecommender extends React.Component { highKP = [sorted_keywords[0].text]; } }); + let kpqv = new KeyphraseQueryView({ keyphrases: ["hello"] }); ext_recs = await this.sendRequest(highKP, api); } diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index becd38a99..356cf52ca 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -276,6 +276,55 @@ export namespace CognitiveServices { } + export namespace HathiTrust { + export const Manager: APIManager = { + converter: (data: string) => { + return data; + }, + requester: async (apiKey: string, query: string) => { + let xhttp = new XMLHttpRequest(); + let serverAddress = "https://babel.hathitrust.org/cgi/htd/​"; + let endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query); + let promisified = (resolve: any, reject: any) => { + xhttp.onreadystatechange = function () { + if (this.readyState === 4) { + let result = xhttp.responseText; + switch (this.status) { + case 200: + return resolve(result); + case 400: + default: + return reject(result); + } + } + }; + + if (apiKey) { + xhttp.open("GET", endpoint, true); + xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey); + xhttp.setRequestHeader('Content-Type', 'application/json'); + xhttp.send(); + } + else { + console.log("API key for BING unavailable"); + } + }; + return new Promise(promisified); + } + + }; + + export namespace Appliers { + export const analyzer = async (query: string, converter: BingConverter) => { + let results = await ExecuteQuery(Service.Bing, Manager, query); + console.log("Bing results: ", results); + const { title_vals, url_vals } = await converter(results); + return { title_vals, url_vals }; + }; + } + + } + export namespace Text { export const Manager: APIManager = { diff --git a/src/client/views/KeyphraseQueryView.scss b/src/client/views/KeyphraseQueryView.scss new file mode 100644 index 000000000..ac715e5e7 --- /dev/null +++ b/src/client/views/KeyphraseQueryView.scss @@ -0,0 +1,8 @@ +.fading { + animation: fanOut 1s +} + +@keyframes fanOut { + from {opacity: 0;} + to {opacity: 1;} +} \ No newline at end of file diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx new file mode 100644 index 000000000..1955399f9 --- /dev/null +++ b/src/client/views/KeyphraseQueryView.tsx @@ -0,0 +1,30 @@ +import { observer } from "mobx-react"; +import React = require("react"); +import "./KeyphraseQueryView.scss"; + +// tslint:disable-next-line: class-name +export interface KP_Props { + keyphrases: string[]; +} + +@observer +export class KeyphraseQueryView extends React.Component{ + constructor(props: KP_Props) { + super(props); + console.log("FIRST KEY PHRASE: ", props.keyphrases[0]); + } + + render() { + return ( +
+

Select queries to send:

+ {this.props.keyphrases.map((kp: string) => { + setTimeout(() => { + return (

{kp}

); + }, 1000); + + })} +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index 64411b5fe..1889a192c 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -247,7 +247,7 @@ display:flex; flex-direction: row; width: 150px; - margin: auto 0 auto auto; + margin: auto auto auto auto; } .react-autosuggest__container { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2455c320d..b82486109 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -46,6 +46,7 @@ import { RecommendationsBox } from '../RecommendationsBox'; import { SearchUtil } from '../../util/SearchUtil'; import { ClientRecommender } from '../../ClientRecommender'; import { SchemaHeaderField } from '../../../new_fields/SchemaHeaderField'; +import { KeyphraseQueryView } from '../KeyphraseQueryView'; library.add(fa.faBrain); library.add(fa.faEdit); @@ -117,6 +118,7 @@ export class DocumentView extends DocComponent(Docu private _hitTemplateDrag = false; private _mainCont = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; + private _showKPQuery: boolean = false; public get ContentDiv() { return this._mainCont.current; } @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @@ -657,6 +659,7 @@ export class DocumentView extends DocComponent(Docu bodies.push(body); } CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); + this._showKPQuery = true; } onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; @@ -807,22 +810,25 @@ export class DocumentView extends DocComponent(Docu const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"]; let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear; - return
Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)} - style={{ - transition: this.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition), - pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all", - color: StrCast(this.Document.color), - outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", - border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, - background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor, - width: animwidth, - height: animheight, - transform: `scale(${this.layoutDoc.fitWidth ? 1 : this.props.ContentScaling()})`, - opacity: this.Document.opacity - }} > - {this.innards} + return
+
Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)} + style={{ + transition: this.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition), + pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all", + color: StrCast(this.Document.color), + outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", + border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, + background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor, + width: animwidth, + height: animheight, + transform: `scale(${this.layoutDoc.fitWidth ? 1 : this.props.ContentScaling()})`, + opacity: this.Document.opacity + }} > + {this.innards} +
+ {this._showKPQuery ? : undefined}
; } } -- cgit v1.2.3-70-g09d2 From b80c7695569304bf7d7312f8159bb1cd9dfec3b4 Mon Sep 17 00:00:00 2001 From: ab Date: Wed, 4 Dec 2019 12:01:25 -0500 Subject: idk --- 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 b82486109..079df9cf4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -828,7 +828,7 @@ export class DocumentView extends DocComponent(Docu }} > {this.innards}
- {this._showKPQuery ? : undefined} + {this._showKPQuery ? : undefined}
; } } -- cgit v1.2.3-70-g09d2 From 07141291bee793955d7061f4e479942d7aceda67 Mon Sep 17 00:00:00 2001 From: ab Date: Wed, 4 Dec 2019 16:08:12 -0500 Subject: user queries stuff --- src/client/ClientRecommender.tsx | 13 +++++++++--- src/client/cognitive_services/CognitiveServices.ts | 2 +- src/client/views/KeyphraseQueryView.tsx | 21 +++++++++++-------- src/client/views/nodes/DocumentView.tsx | 24 +++++++++++++++++----- 4 files changed, 43 insertions(+), 17 deletions(-) (limited to 'src/client/views') diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx index b384a8ebe..83aed0204 100644 --- a/src/client/ClientRecommender.tsx +++ b/src/client/ClientRecommender.tsx @@ -45,6 +45,7 @@ export class ClientRecommender extends React.Component { static Instance: ClientRecommender; private mainDoc?: RecommenderDocument; private docVectors: Set = new Set(); + public _queries: string[] = []; @observable private corr_matrix = [[0, 0], [0, 0]]; // for testing @@ -277,11 +278,17 @@ export class ClientRecommender extends React.Component { const sorted_keywords = response.result.keywords; if (sorted_keywords.length > 0) { console.log("IBM keyphrase", sorted_keywords[0]); - highKP = [sorted_keywords[0].text]; + highKP = []; + for (let i = 0; i < 5; i++) { + if (sorted_keywords[i]) { + highKP.push(sorted_keywords[i].text); + } + } + keyterms = new List(highKP); } }); - let kpqv = new KeyphraseQueryView({ keyphrases: ["hello"] }); - ext_recs = await this.sendRequest(highKP, api); + //let kpqv = new KeyphraseQueryView({ keyphrases: ["hello"] }); + ext_recs = await this.sendRequest([highKP[0]], api); } // keyterms: list for extDoc, kp_string: input to TF, ext_recs: {titles, urls} of retrieved results from highKP query diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 356cf52ca..94532aaaa 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -398,7 +398,7 @@ export namespace CognitiveServices { //await vectorize([data], dataDoc, isMainDoc); await vectorize(kp_string, dataDoc, isMainDoc); } else { - return external_recommendations; + return { recs: external_recommendations, keyterms: keyterms }; } }; diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx index 1955399f9..a9dafc4a4 100644 --- a/src/client/views/KeyphraseQueryView.tsx +++ b/src/client/views/KeyphraseQueryView.tsx @@ -4,7 +4,7 @@ import "./KeyphraseQueryView.scss"; // tslint:disable-next-line: class-name export interface KP_Props { - keyphrases: string[]; + keyphrases: string; } @observer @@ -15,15 +15,20 @@ export class KeyphraseQueryView extends React.Component{ } render() { + let kps = this.props.keyphrases.toString(); + let keyterms = this.props.keyphrases.split(','); return (
-

Select queries to send:

- {this.props.keyphrases.map((kp: string) => { - setTimeout(() => { - return (

{kp}

); - }, 1000); - - })} +
Select queries to send:
+
+ {keyterms.map((kp: string) => { + //return (

{"-" + kp}

); + return (

); + })} +
); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 079df9cf4..91618491c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, runInAction, trace } from "mobx"; +import { action, computed, runInAction, trace, observable } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc"; @@ -119,6 +119,7 @@ export class DocumentView extends DocComponent(Docu private _mainCont = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; private _showKPQuery: boolean = false; + private _queries: string = ""; public get ContentDiv() { return this._mainCont.current; } @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @@ -643,16 +644,28 @@ export class DocumentView extends DocComponent(Docu // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY); } + @action externalRecommendation = async (e: React.MouseEvent, api: string) => { if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); ClientRecommender.Instance.reset_docs(); const doc = Doc.GetDataDoc(this.props.Document); const extdoc = doc.data_ext as Doc; - const values = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false, api); + const recs_and_kps = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false, api); + let recs: any; + let kps: any; + if (recs_and_kps) { + recs = recs_and_kps.recs; + kps = recs_and_kps.keyterms; + } + else { + console.log("recommender system failed :("); + return; + } + console.log("ibm keyterms: ", kps.toString()); const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")]; let bodies: Doc[] = []; - const titles = values.title_vals; - const urls = values.url_vals; + const titles = recs.title_vals; + const urls = recs.url_vals; for (let i = 0; i < 5; i++) { const body = Docs.Create.FreeformDocument([], { title: titles[i] }); body.href = urls[i]; @@ -660,6 +673,7 @@ export class DocumentView extends DocComponent(Docu } CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); this._showKPQuery = true; + this._queries = kps.toString(); } onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; @@ -828,7 +842,7 @@ export class DocumentView extends DocComponent(Docu }} > {this.innards}
- {this._showKPQuery ? : undefined} + {this._showKPQuery ? : undefined}
; } } -- cgit v1.2.3-70-g09d2 From c78409b7cb6c4ab828bce0eef1525032958219f7 Mon Sep 17 00:00:00 2001 From: Fawn Date: Tue, 14 Jan 2020 11:48:19 -0500 Subject: temp fix for inkingstroke type error when on mobile view --- src/client/views/GestureOverlay.tsx | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 830c06f1f..632f2f0d6 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,9 +1,9 @@ import React = require("react"); import { Touchable } from "./Touchable"; import { observer } from "mobx-react"; -import "./GestureOverlay.scss" +import "./GestureOverlay.scss"; import { computed, observable, action, runInAction } from "mobx"; -import { CreatePolyline } from "./InkingStroke"; +// import { CreatePolyline } from "./InkingStroke"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { InteractionUtils } from "../util/InteractionUtils"; import { InkingControl } from "./InkingControl"; @@ -199,6 +199,21 @@ export default class GestureOverlay extends Touchable { return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top }; } + // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view + CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color?: string, width?: number) { + const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); + return ( + + ); + } + @computed get currentStroke() { if (this._points.length <= 1) { return (null); @@ -208,7 +223,7 @@ export default class GestureOverlay extends Touchable { return ( - {CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} + {this.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} ); } -- cgit v1.2.3-70-g09d2 From b4c2d2add2b863060ce559e49b3953f24050f162 Mon Sep 17 00:00:00 2001 From: Fawn Date: Tue, 14 Jan 2020 14:47:44 -0500 Subject: can add strokes to mobile ink --- src/client/views/collections/CollectionSubView.tsx | 3 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/mobile/MobileInterface.tsx | 108 +++++++++++++++------ .../authentication/models/current_user_utils.ts | 4 + 4 files changed, 87 insertions(+), 29 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 73dc7edc6..d8b575092 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -53,7 +53,9 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this.dropDisposer && this.dropDisposer(); this.gestureDisposer && this.gestureDisposer(); + console.log("create drop", ele); if (ele) { + console.log("create drop 2", ele); this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this)); } @@ -135,7 +137,6 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @undoBatch protected onGesture(e: Event, ge: GestureUtils.GestureEvent) { - } @undoBatch diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 84945c6e6..c3e131184 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -356,6 +356,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { + console.log("on gesture"); switch (ge.gesture) { case GestureUtils.Gestures.Stroke: const points = ge.points; diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 1e0920686..8d342d749 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -10,8 +10,10 @@ import { DocumentView } from '../client/views/nodes/DocumentView'; import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils'; import { Transform } from '../client/util/Transform'; import { library } from '@fortawesome/fontawesome-svg-core'; -import { faPenNib, faHighlighter, faEraser, faMousePointer } from '@fortawesome/free-solid-svg-icons'; +import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice } from '@fortawesome/free-solid-svg-icons'; import { Scripting } from '../client/util/Scripting'; +import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView'; +import GestureOverlay from '../client/views/GestureOverlay'; @observer export default class MobileInterface extends React.Component { @@ -35,52 +37,102 @@ export default class MobileInterface extends React.Component { } } - @action switchCurrentView = (view: "main" | "ink" | "library") => { this.currentView = view; } + @action + switchCurrentView = (view: "main" | "ink" | "library") => { + this.currentView = view; + + if (this.userDoc) { + switch (view) { + case "main": { + const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); + this.userDoc.activeMobile = doc; + break; + } + case "ink": { + const doc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc); + this.userDoc.activeMobile = doc; + break; + } + } + } + } @computed get mainContent() { if (this.mainContainer) { - switch (this.currentView) { - case "main": - return window.screen.width} + PanelHeight={() => window.screen.height} + renderDepth={0} + focus={emptyFunction} + backgroundColor={returnEmptyString} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + zoomToScale={emptyFunction} + getScale={returnOne}> + ; + } + return "hello"; + } + + @computed + get inkContent() { + // return
INK
; + if (this.mainContainer) { + return ( + + window.screen.width} PanelHeight={() => window.screen.height} - renderDepth={0} + PanelWidth={() => window.screen.width} + annotationsKey={""} + isAnnotationOverlay={false} focus={emptyFunction} - backgroundColor={returnEmptyString} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} - bringToFront={emptyFunction} + isSelected={returnTrue} // + select={emptyFunction} + active={returnTrue} // + ContentScaling={returnOne} + whenActiveChanged={returnFalse} + CollectionView={undefined} + ScreenToLocalTransform={Transform.Identity} + ruleProvider={undefined} + renderDepth={0} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne}> -
; - case "ink": - return
INK
; - case "library": - return
LIBRARY
; - } + chromeCollapsed={true}> + + + ); } - return "hello"; } render() { - console.log("rendering mobile"); + const content = this.currentView === "main" ? this.mainContent : this.currentView === "ink" ? this.inkContent : <>; return (
- {this.mainContent} + {content}
); } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 71369b625..b40179fbc 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -140,6 +140,10 @@ export class CurrentUserUtils { }); } + static setupMobileInkingDoc(userDoc: Doc) { + return Docs.Create.FreeformDocument([], { x: 0, y: 0, width: 10, height: 20, title: "Mobile Inking", backgroundColor: "red" }); + } + // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. when clicked, this panel will be displayed in the target container (ie, sidebarContainer) static setupToolsPanel(sidebarContainer: Doc, doc: Doc) { // setup a masonry view of all he creators -- cgit v1.2.3-70-g09d2 From 3cca58612cde96a3082ca8e190fe2166d531d556 Mon Sep 17 00:00:00 2001 From: Fawn Date: Tue, 14 Jan 2020 17:40:50 -0500 Subject: drawn strokes get added to mobile ink collection --- src/client/views/GestureOverlay.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 2 - .../collectionFreeForm/CollectionFreeFormView.tsx | 8 +-- src/mobile/MobileInterface.scss | 7 +++ src/mobile/MobileInterface.tsx | 62 ++++++++++++++-------- .../authentication/models/current_user_utils.ts | 2 +- 6 files changed, 53 insertions(+), 30 deletions(-) create mode 100644 src/mobile/MobileInterface.scss (limited to 'src/client/views') diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 632f2f0d6..4d487c032 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -231,9 +231,9 @@ export default class GestureOverlay extends Touchable { render() { return (
+ {this.currentStroke} {this.props.children} {this._palette} - {this.currentStroke}
); } } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d8b575092..e94f24f2c 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -53,9 +53,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this.dropDisposer && this.dropDisposer(); this.gestureDisposer && this.gestureDisposer(); - console.log("create drop", ele); if (ele) { - console.log("create drop 2", ele); this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this)); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c3e131184..8d376fb57 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -114,6 +114,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } private addDocument = (newBox: Doc) => { const added = this.props.addDocument(newBox); + console.log("adding doc from freeform", added); added && this.bringToFront(newBox); added && this.updateCluster(newBox); return added; @@ -356,12 +357,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { - console.log("on gesture"); switch (ge.gesture) { case GestureUtils.Gestures.Stroke: const points = ge.points; const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height }); + console.log("make stroke", inkDoc); this.addDocument(inkDoc); e.stopPropagation(); break; @@ -403,14 +404,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => { // I think it makes sense for the marquee menu to go away when panned. -syip2 - MarqueeOptionsMenu.Instance.fadeOut(true); + MarqueeOptionsMenu.Instance && MarqueeOptionsMenu.Instance.fadeOut(true); let x = this.Document.panX || 0; let y = this.Document.panY || 0; const docs = this.childLayoutPairs.map(pair => pair.layout); const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay) { - PDFMenu.Instance.fadeOut(true); + PDFMenu.Instance && PDFMenu.Instance.fadeOut(true); const minx = docs.length ? NumCast(docs[0].x) : 0; const maxx = docs.length ? NumCast(docs[0].width) + minx : minx; const miny = docs.length ? NumCast(docs[0].y) : 0; @@ -990,6 +991,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document if (!this.extensionDoc) return (null); // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale; + console.log("height freeform", this.isAnnotationOverlay, this.Document.scrollHeight, this.props.PanelHeight()); return
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss new file mode 100644 index 000000000..e4cc919a5 --- /dev/null +++ b/src/mobile/MobileInterface.scss @@ -0,0 +1,7 @@ +.mobileInterface-topButtons { + position: absolute; + display: flex; + justify-content: space-between; + width: 100%; + z-index: 9999; +} \ No newline at end of file diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 8d342d749..3cb08afa7 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -10,10 +10,16 @@ import { DocumentView } from '../client/views/nodes/DocumentView'; import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils'; import { Transform } from '../client/util/Transform'; import { library } from '@fortawesome/fontawesome-svg-core'; -import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice } from '@fortawesome/free-solid-svg-icons'; +import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck } from '@fortawesome/free-solid-svg-icons'; import { Scripting } from '../client/util/Scripting'; import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView'; import GestureOverlay from '../client/views/GestureOverlay'; +import { InkingControl } from '../client/views/InkingControl'; +import { InkTool } from '../new_fields/InkField'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import "./MobileInterface.scss"; + +library.add(faTrash, faCheck); @observer export default class MobileInterface extends React.Component { @@ -22,6 +28,9 @@ export default class MobileInterface extends React.Component { @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; } @observable private currentView: "main" | "ink" | "library" = "main"; + private mainDoc = CurrentUserUtils.setupMobileDoc(this.userDoc); + private inkDoc?: Doc; + constructor(props: Readonly<{}>) { super(props); MobileInterface.Instance = this; @@ -32,8 +41,8 @@ export default class MobileInterface extends React.Component { library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]); if (this.userDoc && !this.mainContainer) { - const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); - this.userDoc.activeMobile = doc; + // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); + this.userDoc.activeMobile = this.mainDoc; } } @@ -44,13 +53,14 @@ export default class MobileInterface extends React.Component { if (this.userDoc) { switch (view) { case "main": { - const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); - this.userDoc.activeMobile = doc; + // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); + this.userDoc.activeMobile = this.mainDoc; break; } case "ink": { - const doc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc); - this.userDoc.activeMobile = doc; + this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc); + this.userDoc.activeMobile = this.inkDoc; + InkingControl.Instance.switchTool(InkTool.Pen); break; } } @@ -64,7 +74,7 @@ export default class MobileInterface extends React.Component { Document={this.mainContainer} DataDoc={undefined} LibraryPath={emptyPath} - addDocument={undefined} + addDocument={returnFalse} addDocTab={returnFalse} pinToPres={emptyFunction} removeDocument={undefined} @@ -89,40 +99,46 @@ export default class MobileInterface extends React.Component { return "hello"; } + onClick = (e: React.MouseEvent) => { + // insert ink as collection into active displayed doc view + // reset ink collection + this.inkDoc = undefined; + console.log("clicked"); + this.switchCurrentView("main"); + InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool + } + @computed get inkContent() { - // return
INK
; + // TODO: get props from active collection and pass to the new freeform if (this.mainContainer) { return ( - + + +
+ window.screen.height} - PanelWidth={() => window.screen.width} - annotationsKey={""} - isAnnotationOverlay={false} + PanelHeight={() => window.innerHeight} + PanelWidth={() => window.innerWidth} focus={emptyFunction} - isSelected={returnTrue} // + isSelected={returnTrue} select={emptyFunction} - active={returnTrue} // + active={returnTrue} ContentScaling={returnOne} whenActiveChanged={returnFalse} - CollectionView={undefined} ScreenToLocalTransform={Transform.Identity} ruleProvider={undefined} renderDepth={0} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - chromeCollapsed={true}> - + ContainingCollectionDoc={undefined}> + ); } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index b40179fbc..98092ddfe 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -141,7 +141,7 @@ export class CurrentUserUtils { } static setupMobileInkingDoc(userDoc: Doc) { - return Docs.Create.FreeformDocument([], { x: 0, y: 0, width: 10, height: 20, title: "Mobile Inking", backgroundColor: "red" }); + return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" }); } // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. when clicked, this panel will be displayed in the target container (ie, sidebarContainer) -- cgit v1.2.3-70-g09d2 From 65e164eaec42d1850de7f5e1eba1d4302c3e8230 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 15 Jan 2020 16:08:48 -0500 Subject: mobile interface emits events when switched to inking view and when strokes are drawn, currently with dummy callbacks --- src/client/DocServer.ts | 22 ++++++++++++++++++- src/client/views/GestureOverlay.tsx | 21 ++++++++++++++++++ src/client/views/MainView.tsx | 13 +++++++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 --- src/mobile/MobileInterface.tsx | 25 +++++++++++++++------- src/server/Message.ts | 13 +++++++++++ src/server/Websocket/Websocket.ts | 12 ++++++++++- 7 files changed, 94 insertions(+), 15 deletions(-) (limited to 'src/client/views') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index ed7fbd7ba..cfd9612ed 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,10 +1,11 @@ import * as OpenSocket from 'socket.io-client'; -import { MessageStore, YoutubeQueryTypes } from "./../server/Message"; +import { MessageStore, YoutubeQueryTypes, GestureContent } from "./../server/Message"; import { Opt, Doc } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../new_fields/RefField'; import { Id, HandleUpdate } from '../new_fields/FieldSymbols'; +import GestureOverlay from './views/GestureOverlay'; /** * This class encapsulates the transfer and cross-client synchronization of @@ -64,6 +65,19 @@ export namespace DocServer { } } + export namespace Mobile { + + export function dispatchGesturePoints(content: GestureContent) { + Utils.Emit(_socket, MessageStore.GesturePoints, content); + } + + export function dispatchBoxTrigger(enableBox: boolean) { + // _socket.emit("dispatchBoxTrigger"); + Utils.Emit(_socket, MessageStore.MobileInkBoxTrigger, enableBox); + } + + } + export function init(protocol: string, hostname: string, port: number, identifier: string) { _cache = {}; GUID = identifier; @@ -85,6 +99,12 @@ export namespace DocServer { Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, () => { alert("Your connection to the server has been terminated."); }); + _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => { + GestureOverlay.Instance.manualDispatch(content); + }); + _socket.addEventListener("receiveBoxTrigger", (enableBox: boolean) => { + GestureOverlay.Instance.showBox(enableBox); + }); } function errorFunc(): never { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 4d487c032..4b8dfa119 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -16,6 +16,10 @@ import { Scripting } from "../util/Scripting"; import { FieldValue, Cast } from "../../new_fields/Types"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import Palette from "./Palette"; +import MobileInterface from "../../mobile/MobileInterface"; +import { MainView } from "./MainView"; +import { DocServer } from "../DocServer"; +import { GestureContent } from "../../server/Message"; @observer export default class GestureOverlay extends Touchable { @@ -35,6 +39,14 @@ export default class GestureOverlay extends Touchable { GestureOverlay.Instance = this; } + manualDispatch = (content: GestureContent) => { + console.log(content); + } + + showBox = (enableBox: boolean) => { + console.log("enable box?", enableBox); + } + @action handleHandDown = (e: React.TouchEvent) => { const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); @@ -139,6 +151,15 @@ export default class GestureOverlay extends Touchable { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); + if (MobileInterface.Instance.drawingInk) { + const { selectedColor, selectedWidth } = InkingControl.Instance; + DocServer.Mobile.dispatchGesturePoints({ + points: this._points, + color: selectedColor, + width: selectedWidth + }); + } + const result = GestureUtils.GestureRecognizer.Recognize(new Array(points)); let actionPerformed = false; if (result && result.Score > 0.7) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 93fb0a07c..f1f7c37a3 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -49,6 +49,7 @@ export class MainView extends React.Component { private _flyoutSizeOnDown = 0; private _urlState: HistoryUtil.DocUrl; private _docBtnRef = React.createRef(); + private _mainViewRef = React.createRef(); @observable private _panelWidth: number = 0; @observable private _panelHeight: number = 0; @@ -507,8 +508,16 @@ export class MainView extends React.Component { return (null); } + get mainViewElement() { + return document.getElementById("mainView-container"); + } + + get mainViewRef() { + return this._mainViewRef; + } + render() { - return (
+ return (
@@ -518,7 +527,7 @@ export class MainView extends React.Component { - + diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8d376fb57..bbfd22f50 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -114,7 +114,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } private addDocument = (newBox: Doc) => { const added = this.props.addDocument(newBox); - console.log("adding doc from freeform", added); added && this.bringToFront(newBox); added && this.updateCluster(newBox); return added; @@ -362,7 +361,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const points = ge.points; const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height }); - console.log("make stroke", inkDoc); this.addDocument(inkDoc); e.stopPropagation(); break; @@ -991,7 +989,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document if (!this.extensionDoc) return (null); // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale; - console.log("height freeform", this.isAnnotationOverlay, this.Document.scrollHeight, this.props.PanelHeight()); return
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 3cb08afa7..1429226b4 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -2,7 +2,7 @@ import React = require('react'); import { observer } from 'mobx-react'; import { computed, action, observable } from 'mobx'; import { CurrentUserUtils } from '../server/authentication/models/current_user_utils'; -import { FieldValue, Cast } from '../new_fields/Types'; +import { FieldValue, Cast, StrCast } from '../new_fields/Types'; import { Doc } from '../new_fields/Doc'; import { Docs } from '../client/documents/Documents'; import { CollectionView } from '../client/views/collections/CollectionView'; @@ -18,6 +18,10 @@ import { InkingControl } from '../client/views/InkingControl'; import { InkTool } from '../new_fields/InkField'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import "./MobileInterface.scss"; +import { SelectionManager } from '../client/util/SelectionManager'; +import { DateField } from '../new_fields/DateField'; +import { GestureUtils } from '../pen-gestures/GestureUtils'; +import { DocServer } from '../client/DocServer'; library.add(faTrash, faCheck); @@ -30,6 +34,7 @@ export default class MobileInterface extends React.Component { private mainDoc = CurrentUserUtils.setupMobileDoc(this.userDoc); private inkDoc?: Doc; + public drawingInk: boolean = false; constructor(props: Readonly<{}>) { super(props); @@ -61,6 +66,10 @@ export default class MobileInterface extends React.Component { this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc); this.userDoc.activeMobile = this.inkDoc; InkingControl.Instance.switchTool(InkTool.Pen); + this.drawingInk = true; + + DocServer.Mobile.dispatchBoxTrigger(true); + break; } } @@ -100,17 +109,17 @@ export default class MobileInterface extends React.Component { } onClick = (e: React.MouseEvent) => { - // insert ink as collection into active displayed doc view - // reset ink collection - this.inkDoc = undefined; - console.log("clicked"); this.switchCurrentView("main"); InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool + + DocServer.Mobile.dispatchBoxTrigger(false); + + this.inkDoc = undefined; + this.drawingInk = false; } @computed get inkContent() { - // TODO: get props from active collection and pass to the new freeform if (this.mainContainer) { return ( @@ -128,9 +137,9 @@ export default class MobileInterface extends React.Component { PanelHeight={() => window.innerHeight} PanelWidth={() => window.innerWidth} focus={emptyFunction} - isSelected={returnTrue} + isSelected={returnFalse} select={emptyFunction} - active={returnTrue} + active={returnFalse} ContentScaling={returnOne} whenActiveChanged={returnFalse} ScreenToLocalTransform={Transform.Identity} diff --git a/src/server/Message.ts b/src/server/Message.ts index 621abfd1e..fe74dafa5 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,4 +1,5 @@ import { Utils } from "../Utils"; +import { Point } from "../pen-gestures/ndollar"; export class Message { private _name: string; @@ -42,6 +43,16 @@ export interface Diff extends Reference { readonly diff: any; } +export interface GestureContent { + readonly points: Array; + readonly width?: string; + readonly color?: string; +} + +export interface MobileInkBoxTriggerContent { + readonly enableBox: boolean; +} + export namespace MessageStore { export const Foo = new Message("Foo"); export const Bar = new Message("Bar"); @@ -51,6 +62,8 @@ export namespace MessageStore { export const GetDocument = new Message("Get Document"); export const DeleteAll = new Message("Delete All"); export const ConnectionTerminated = new Message("Connection Terminated"); + export const GesturePoints = new Message("Gesture Points"); + export const MobileInkBoxTrigger = new Message("Trigger Mobile Ink Box"); export const GetRefField = new Message("Get Ref Field"); export const GetRefFields = new Message("Get Ref Fields"); diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index 578147d60..67777f023 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -1,5 +1,5 @@ import { Utils } from "../../Utils"; -import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes } from "../Message"; +import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent } from "../Message"; import { Client } from "../Client"; import { Socket } from "socket.io"; import { Database } from "../database"; @@ -54,6 +54,8 @@ export namespace WebSocket { Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff)); Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id)); Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids)); + Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content)); + Utils.AddServerHandler(socket, MessageStore.MobileInkBoxTrigger, enableBox => processBoxTrigger(socket, enableBox)); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); @@ -68,6 +70,14 @@ export namespace WebSocket { logPort("websocket", socketPort); } + function processGesturePoints(socket: Socket, content: GestureContent) { + socket.broadcast.emit("receiveGesturePoints", content); + } + + function processBoxTrigger(socket: Socket, enableBox: boolean) { + socket.broadcast.emit("receiveBoxTrigger", enableBox); + } + function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) { const { ProjectCredentials } = GoogleCredentialsLoader; switch (query.type) { -- cgit v1.2.3-70-g09d2 From 43c4ff69d515b874f71f9c638a267cd896036854 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 15 Jan 2020 17:20:09 -0500 Subject: strokes from mobile interface get properly inserted into desktop with offset of the mobile ink overlay --- src/client/DocServer.ts | 12 +++---- src/client/views/GestureOverlay.scss | 4 +++ src/client/views/GestureOverlay.tsx | 70 ++++++++++++++++++++++++++++++------ src/mobile/MobileInterface.tsx | 12 +++++-- src/server/Message.ts | 7 ++-- src/server/Websocket/Websocket.ts | 8 ++--- 6 files changed, 89 insertions(+), 24 deletions(-) (limited to 'src/client/views') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index cfd9612ed..132128182 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,5 +1,5 @@ import * as OpenSocket from 'socket.io-client'; -import { MessageStore, YoutubeQueryTypes, GestureContent } from "./../server/Message"; +import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkBoxContent } from "./../server/Message"; import { Opt, Doc } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; @@ -71,9 +71,9 @@ export namespace DocServer { Utils.Emit(_socket, MessageStore.GesturePoints, content); } - export function dispatchBoxTrigger(enableBox: boolean) { + export function dispatchBoxTrigger(content: MobileInkBoxContent) { // _socket.emit("dispatchBoxTrigger"); - Utils.Emit(_socket, MessageStore.MobileInkBoxTrigger, enableBox); + Utils.Emit(_socket, MessageStore.MobileInkBoxTrigger, content); } } @@ -100,10 +100,10 @@ export namespace DocServer { alert("Your connection to the server has been terminated."); }); _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => { - GestureOverlay.Instance.manualDispatch(content); + GestureOverlay.Instance.drawStrokeToMobileInkBox(content); }); - _socket.addEventListener("receiveBoxTrigger", (enableBox: boolean) => { - GestureOverlay.Instance.showBox(enableBox); + _socket.addEventListener("receiveBoxTrigger", (content: MobileInkBoxContent) => { + GestureOverlay.Instance.enableMobileInkBox(content); }); } diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index 31601efd4..2996b7073 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -5,4 +5,8 @@ top: 0; left: 0; touch-action: none; +} + +.mobileInkOverlay { + border: 5px dashed red; } \ No newline at end of file diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 4b8dfa119..7e74d1d33 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -19,7 +19,8 @@ import Palette from "./Palette"; import MobileInterface from "../../mobile/MobileInterface"; import { MainView } from "./MainView"; import { DocServer } from "../DocServer"; -import { GestureContent } from "../../server/Message"; +import { GestureContent, MobileInkBoxContent } from "../../server/Message"; +import { Point } from "../northstar/model/idea/idea"; @observer export default class GestureOverlay extends Touchable { @@ -30,6 +31,10 @@ export default class GestureOverlay extends Touchable { @observable public Color: string = "rgb(244, 67, 54)"; @observable public Width: number = 5; + @observable private showMobileInkOverlay: boolean = false; + @observable private mobileInkOverlaySize: { width: number, height: number } = { width: 500, height: 700 }; + @observable private mobileInkOverlayPosition: { x: number, y: number } = { x: 300, y: 0 }; + private _d1: Doc | undefined; private thumbIdentifier?: number; @@ -39,14 +44,6 @@ export default class GestureOverlay extends Touchable { GestureOverlay.Instance = this; } - manualDispatch = (content: GestureContent) => { - console.log(content); - } - - showBox = (enableBox: boolean) => { - console.log("enable box?", enableBox); - } - @action handleHandDown = (e: React.TouchEvent) => { const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); @@ -151,10 +148,11 @@ export default class GestureOverlay extends Touchable { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - if (MobileInterface.Instance.drawingInk) { + if (MobileInterface.Instance && MobileInterface.Instance.drawingInk) { const { selectedColor, selectedWidth } = InkingControl.Instance; DocServer.Mobile.dispatchGesturePoints({ points: this._points, + bounds: B, color: selectedColor, width: selectedWidth }); @@ -249,12 +247,64 @@ export default class GestureOverlay extends Touchable { ); } + drawStrokeToMobileInkBox = (content: GestureContent) => { + console.log(content); + const { points, bounds } = content; + + const B = { + right: bounds.right + this.mobileInkOverlayPosition.x, + left: bounds.left + this.mobileInkOverlayPosition.x, + bottom: bounds.bottom + this.mobileInkOverlayPosition.y, + top: bounds.top + this.mobileInkOverlayPosition.y, + width: bounds.width, + height: bounds.height + }; + + const target = document.elementFromPoint(points[0].X, points[0].Y); + target?.dispatchEvent( + new CustomEvent("dashOnGesture", + { + bubbles: true, + detail: { + points: points, + gesture: GestureUtils.Gestures.Stroke, + bounds: B + } + } + ) + ); + } + + @action + enableMobileInkBox = (content: MobileInkBoxContent) => { + const { enableBox, width, height } = content; + console.log("enable box?", enableBox, width, height); + this.showMobileInkOverlay = enableBox; + this.mobileInkOverlaySize = { width: width ? width : 0, height: height ? height : 0 }; + this.mobileInkOverlayPosition = { x: 300, y: 25 }; // TODO put at center of screen + + } + + @computed get mobileInkOverlay() { + return ( +
+ ); + } + render() { return (
{this.currentStroke} {this.props.children} {this._palette} + {this.showMobileInkOverlay ? this.mobileInkOverlay : <>}
); } } diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 1429226b4..4840ea374 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -68,7 +68,11 @@ export default class MobileInterface extends React.Component { InkingControl.Instance.switchTool(InkTool.Pen); this.drawingInk = true; - DocServer.Mobile.dispatchBoxTrigger(true); + DocServer.Mobile.dispatchBoxTrigger({ + enableBox: true, + width: window.innerWidth, + height: window.innerHeight + }); break; } @@ -112,7 +116,11 @@ export default class MobileInterface extends React.Component { this.switchCurrentView("main"); InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool - DocServer.Mobile.dispatchBoxTrigger(false); + DocServer.Mobile.dispatchBoxTrigger({ + enableBox: false, + width: window.innerWidth, + height: window.innerHeight + }); this.inkDoc = undefined; this.drawingInk = false; diff --git a/src/server/Message.ts b/src/server/Message.ts index fe74dafa5..1958286df 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -45,12 +45,15 @@ export interface Diff extends Reference { export interface GestureContent { readonly points: Array; + readonly bounds: { right: number, left: number, bottom: number, top: number, width: number, height: number }; readonly width?: string; readonly color?: string; } -export interface MobileInkBoxTriggerContent { +export interface MobileInkBoxContent { readonly enableBox: boolean; + readonly width?: number; + readonly height?: number; } export namespace MessageStore { @@ -63,7 +66,7 @@ export namespace MessageStore { export const DeleteAll = new Message("Delete All"); export const ConnectionTerminated = new Message("Connection Terminated"); export const GesturePoints = new Message("Gesture Points"); - export const MobileInkBoxTrigger = new Message("Trigger Mobile Ink Box"); + export const MobileInkBoxTrigger = new Message("Trigger Mobile Ink Box"); export const GetRefField = new Message("Get Ref Field"); export const GetRefFields = new Message("Get Ref Fields"); diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index 67777f023..16e34bdfc 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -1,5 +1,5 @@ import { Utils } from "../../Utils"; -import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent } from "../Message"; +import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkBoxContent } from "../Message"; import { Client } from "../Client"; import { Socket } from "socket.io"; import { Database } from "../database"; @@ -55,7 +55,7 @@ export namespace WebSocket { Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id)); Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids)); Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content)); - Utils.AddServerHandler(socket, MessageStore.MobileInkBoxTrigger, enableBox => processBoxTrigger(socket, enableBox)); + Utils.AddServerHandler(socket, MessageStore.MobileInkBoxTrigger, content => processBoxTrigger(socket, content)); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); @@ -74,8 +74,8 @@ export namespace WebSocket { socket.broadcast.emit("receiveGesturePoints", content); } - function processBoxTrigger(socket: Socket, enableBox: boolean) { - socket.broadcast.emit("receiveBoxTrigger", enableBox); + function processBoxTrigger(socket: Socket, content: MobileInkBoxContent) { + socket.broadcast.emit("receiveBoxTrigger", content); } function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) { -- cgit v1.2.3-70-g09d2 From e08c0d5b228c8ca1103bb0d7368f3f65bb24b675 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 15 Jan 2020 17:49:37 -0500 Subject: abstracted mobile ink overlay stuff into its own component --- src/client/DocServer.ts | 4 +- src/client/views/GestureOverlay.tsx | 53 ++------------------------ src/mobile/MobileInkOverlay.tsx | 75 +++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 51 deletions(-) create mode 100644 src/mobile/MobileInkOverlay.tsx (limited to 'src/client/views') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 132128182..a1cb42df2 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -6,6 +6,7 @@ import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../new_fields/RefField'; import { Id, HandleUpdate } from '../new_fields/FieldSymbols'; import GestureOverlay from './views/GestureOverlay'; +import MobileInkOverlay from '../mobile/MobileInkOverlay'; /** * This class encapsulates the transfer and cross-client synchronization of @@ -100,10 +101,11 @@ export namespace DocServer { alert("Your connection to the server has been terminated."); }); _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => { - GestureOverlay.Instance.drawStrokeToMobileInkBox(content); + MobileInkOverlay.Instance.drawStroke(content); }); _socket.addEventListener("receiveBoxTrigger", (content: MobileInkBoxContent) => { GestureOverlay.Instance.enableMobileInkBox(content); + MobileInkOverlay.Instance.initMobileInkOverlay(content); }); } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 7e74d1d33..81284b543 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -21,6 +21,7 @@ import { MainView } from "./MainView"; import { DocServer } from "../DocServer"; import { GestureContent, MobileInkBoxContent } from "../../server/Message"; import { Point } from "../northstar/model/idea/idea"; +import MobileInkOverlay from "../../mobile/MobileInkOverlay"; @observer export default class GestureOverlay extends Touchable { @@ -32,8 +33,6 @@ export default class GestureOverlay extends Touchable { @observable public Width: number = 5; @observable private showMobileInkOverlay: boolean = false; - @observable private mobileInkOverlaySize: { width: number, height: number } = { width: 500, height: 700 }; - @observable private mobileInkOverlayPosition: { x: number, y: number } = { x: 300, y: 0 }; private _d1: Doc | undefined; private thumbIdentifier?: number; @@ -247,55 +246,9 @@ export default class GestureOverlay extends Touchable { ); } - drawStrokeToMobileInkBox = (content: GestureContent) => { - console.log(content); - const { points, bounds } = content; - - const B = { - right: bounds.right + this.mobileInkOverlayPosition.x, - left: bounds.left + this.mobileInkOverlayPosition.x, - bottom: bounds.bottom + this.mobileInkOverlayPosition.y, - top: bounds.top + this.mobileInkOverlayPosition.y, - width: bounds.width, - height: bounds.height - }; - - const target = document.elementFromPoint(points[0].X, points[0].Y); - target?.dispatchEvent( - new CustomEvent("dashOnGesture", - { - bubbles: true, - detail: { - points: points, - gesture: GestureUtils.Gestures.Stroke, - bounds: B - } - } - ) - ); - } - @action enableMobileInkBox = (content: MobileInkBoxContent) => { - const { enableBox, width, height } = content; - console.log("enable box?", enableBox, width, height); - this.showMobileInkOverlay = enableBox; - this.mobileInkOverlaySize = { width: width ? width : 0, height: height ? height : 0 }; - this.mobileInkOverlayPosition = { x: 300, y: 25 }; // TODO put at center of screen - - } - - @computed get mobileInkOverlay() { - return ( -
- ); + this.showMobileInkOverlay = content.enableBox; } render() { @@ -304,7 +257,7 @@ export default class GestureOverlay extends Touchable { {this.currentStroke} {this.props.children} {this._palette} - {this.showMobileInkOverlay ? this.mobileInkOverlay : <>} + {this.showMobileInkOverlay ? : <>}
); } } diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx new file mode 100644 index 000000000..240d23b32 --- /dev/null +++ b/src/mobile/MobileInkOverlay.tsx @@ -0,0 +1,75 @@ +import React = require('react'); +import { observer } from "mobx-react"; +import { MobileInkBoxContent, GestureContent } from "../server/Message"; +import { observable, action } from "mobx"; +import { GestureUtils } from "../pen-gestures/GestureUtils"; + + +@observer +export default class MobileInkOverlay extends React.Component { + public static Instance: MobileInkOverlay; + + private _mobileWidth: number = 0; + private _mobileHeight: number = 0; + @observable private _width: number = 0; + @observable private _height: number = 0; + @observable private _x: number = -300; + @observable private _y: number = -300; + + constructor(props: Readonly<{}>) { + super(props); + MobileInkOverlay.Instance = this; + } + + @action + initMobileInkOverlay(content: MobileInkBoxContent) { + const { width, height } = content; + this._mobileWidth = width ? width : 0; + this._mobileHeight = height ? height : 0; + this._width = width ? width : 0; + this._height = height ? height : 0; + this._x = 300; // TODO: center on screen + this._y = 25; // TODO: center on screen + } + + drawStroke = (content: GestureContent) => { + const { points, bounds } = content; + const scale = 1; + + const B = { + right: bounds.right + this._x, + left: bounds.left + this._x, // TODO: scale + bottom: bounds.bottom + this._y, + top: bounds.top + this._y, // TODO: scale + width: bounds.width * scale, + height: bounds.height * scale, + }; + + const target = document.elementFromPoint(points[0].X, points[0].Y); + target?.dispatchEvent( + new CustomEvent("dashOnGesture", + { + bubbles: true, + detail: { + points: points, + gesture: GestureUtils.Gestures.Stroke, + bounds: B + } + } + ) + ); + } + + render() { + return ( +
+ ); + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 0a5728e57c658188a9cbc881fbceeafed5d99fa7 Mon Sep 17 00:00:00 2001 From: andrewdkim Date: Tue, 21 Jan 2020 14:11:27 -0500 Subject: ignore strokes --- package-lock.json | 13 +++++++++++-- src/client/views/GestureOverlay.tsx | 22 ++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) (limited to 'src/client/views') diff --git a/package-lock.json b/package-lock.json index da0027d07..d5945d554 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15567,6 +15567,15 @@ "readable-stream": "^2.0.2" } }, + "stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", + "requires": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", @@ -15639,7 +15648,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -18236,4 +18245,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 84d089b47..9a17e9eee 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -358,13 +358,30 @@ export default class GestureOverlay extends Touchable { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - const xInGlass = points[0].X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && points[0].X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height; - const yInGlass = points[0].Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && points[0].Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); + 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; + const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { switch (this.Tool) { case ToolglassTools.InkToText: break; + case ToolglassTools.IgnoreGesture: + const target = document.elementFromPoint(this._points[0].X, this._points[0].Y); + target?.dispatchEvent( + new CustomEvent("dashOnGesture", + { + bubbles: true, + detail: { + points: this._points, + gesture: GestureUtils.Gestures.Stroke, + bounds: B + } + } + ) + ); + this._points = []; + break; } } else { @@ -514,6 +531,7 @@ export default class GestureOverlay extends Touchable { export enum ToolglassTools { InkToText = "inktotext", + IgnoreGesture = "ignoregesture", None = "none", } -- cgit v1.2.3-70-g09d2 From d1c1e9f10b8523270a872c6f67df25ebb1d9aa25 Mon Sep 17 00:00:00 2001 From: kimdahey Date: Mon, 27 Jan 2020 20:34:27 -0500 Subject: switching event type, some bugs :( --- src/client/views/Touchable.tsx | 65 +++++++++++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 94 +++++++++------------- 3 files changed, 100 insertions(+), 60 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 7800c4019..eed72e3c5 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -8,9 +8,12 @@ const HOLD_DURATION = 1000; export abstract class Touchable extends React.Component { //private holdTimer: NodeJS.Timeout | undefined; - private holdTimer: NodeJS.Timeout | undefined; + holdTimer: NodeJS.Timeout | undefined; private moveDisposer?: InteractionUtils.MultiTouchEventDisposer; private endDisposer?: InteractionUtils.MultiTouchEventDisposer; + private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer; + private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer; + protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; protected _touchDrag: boolean = false; @@ -65,8 +68,10 @@ export abstract class Touchable extends React.Component { // clearTimeout(this.holdTimer) // this.holdTimer = undefined; // } - this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(te, me), HOLD_DURATION); - // e.stopPropagation(); + console.log(this.holdTimer); + if (!this.holdTimer) { + this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e, me), HOLD_DURATION); + } // console.log(this.holdTimer); break; case 2: @@ -128,7 +133,12 @@ export abstract class Touchable extends React.Component { } } if (this.holdTimer) { + console.log(this.holdTimer); clearTimeout(this.holdTimer); + console.log(this.holdTimer); + + this.holdTimer = undefined; + console.log(this.holdTimer); console.log("clear"); } this._touchDrag = false; @@ -174,10 +184,17 @@ export abstract class Touchable extends React.Component { this.addEndListeners(); } - handle1PointerHoldStart = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { + handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); - e.preventDefault(); + me.touchEvent.stopPropagation(); + this.holdTimer = undefined; this.removeMoveListeners(); + this.removeEndListeners(); + this.removeHoldMoveListeners(); + this.removeHoldEndListeners(); + this.addHoldMoveListeners(); + this.addHoldEndListeners(); + } addMoveListeners = () => { @@ -200,6 +217,44 @@ export abstract class Touchable extends React.Component { this.endDisposer && this.endDisposer(); } + addHoldMoveListeners = () => { + const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent>).detail); + document.addEventListener("dashOnTouchHoldMove", handler); + this.holdMoveDisposer = () => document.removeEventListener("dashOnTouchHoldMove", handler); + } + + addHoldEndListeners = () => { + const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent>).detail); + document.addEventListener("dashOnTouchHoldEnd", handler); + this.holdEndDisposer = () => document.removeEventListener("dashOnTouchHoldEnd", handler); + } + + removeHoldMoveListeners = () => { + this.holdMoveDisposer && this.holdMoveDisposer(); + } + + removeHoldEndListeners = () => { + this.holdEndDisposer && this.holdEndDisposer(); + } + + + handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { + e.stopPropagation(); + me.touchEvent.stopPropagation(); + } + + + handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { + e.stopPropagation(); + me.touchEvent.stopPropagation(); + this.removeHoldMoveListeners(); + this.removeHoldEndListeners(); + + me.touchEvent.stopPropagation(); + me.touchEvent.preventDefault(); + } + + handleHandDown = (e: React.TouchEvent) => { // e.stopPropagation(); // e.preventDefault(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index aaa585b55..2622d9b28 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -494,6 +494,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); const pt1 = myTouches[0]; const pt2 = myTouches[1]; + console.log(myTouches); if (this.prevPoints.size === 2) { const oldPoint1 = this.prevPoints.get(pt1.identifier); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0b6a284d6..ae2ee9e41 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -119,61 +119,45 @@ export class DocumentView extends DocComponent(Docu private _firstY: number = 0; - // handle1PointerHoldStart = (e: React.TouchEvent): any => { - // this.onRadialMenu(e); - // const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0]; - // this._firstX = pt.pageX; - // this._firstY = pt.pageY; - // e.stopPropagation(); - // e.preventDefault(); - - // document.removeEventListener("touchmove", this.onTouch); - // document.removeEventListener("touchmove", this.handle1PointerHoldMove); - // document.addEventListener("touchmove", this.handle1PointerHoldMove); - // document.removeEventListener("touchend", this.handle1PointerHoldEnd); - // document.addEventListener("touchend", this.handle1PointerHoldEnd); - // } - - // handle1PointerHoldMove = (e: TouchEvent): void => { - // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - // if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { - // this.handleRelease(); - // } - // document.removeEventListener("touchmove", this.handle1PointerHoldMove); - // document.addEventListener("touchmove", this.handle1PointerHoldMove); - // document.removeEventListener("touchend", this.handle1PointerHoldEnd); - // document.addEventListener("touchend", this.handle1PointerHoldEnd); - // } - - // handleRelease() { - // RadialMenu.Instance.closeMenu(); - // document.removeEventListener("touchmove", this.handle1PointerHoldMove); - // document.removeEventListener("touchend", this.handle1PointerHoldEnd); - // } - - // handle1PointerHoldEnd = (e: TouchEvent): void => { - // RadialMenu.Instance.closeMenu(); - // document.removeEventListener("touchmove", this.handle1PointerHoldMove); - // document.removeEventListener("touchend", this.handle1PointerHoldEnd); - // } - - // @action - // onRadialMenu = (e: React.TouchEvent): void => { - // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - - // RadialMenu.Instance.openMenu(); - - // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 }); - // RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 }); - // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 }); - // RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); - - // RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15); - // if (!SelectionManager.IsSelected(this, true)) { - // SelectionManager.SelectDoc(this, false); - // } - // e.stopPropagation(); - // } + handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { + console.log("S"); + this.onRadialMenu(e, me); + const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + this._firstX = pt.pageX; + this._firstY = pt.pageY; + + } + + handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { + console.log("K"); + const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { + this.handle1PointerHoldEnd(e, me); + } + } + + handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { + console.log("E"); + RadialMenu.Instance.closeMenu(); + } + + @action + onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { + const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + + RadialMenu.Instance.openMenu(); + + RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); + + RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15); + if (!SelectionManager.IsSelected(this, true)) { + SelectionManager.SelectDoc(this, false); + } + e.stopPropagation(); + } @action componentDidMount() { -- cgit v1.2.3-70-g09d2 From 9bf6bd83bdb136e4478c59ce69ee44fc5d0d8352 Mon Sep 17 00:00:00 2001 From: kimdahey Date: Wed, 29 Jan 2020 16:56:33 -0500 Subject: fixes --- src/client/util/InteractionUtils.ts | 13 ++++++++++++- src/client/views/GestureOverlay.tsx | 32 ++++++++++++++++++++++++++++++++ src/client/views/Touchable.tsx | 24 ++++-------------------- src/client/views/nodes/DocumentView.tsx | 8 ++++++++ 4 files changed, 56 insertions(+), 21 deletions(-) (limited to 'src/client/views') diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts index da42bdc93..8e1beecb7 100644 --- a/src/client/util/InteractionUtils.ts +++ b/src/client/util/InteractionUtils.ts @@ -23,7 +23,7 @@ export namespace InteractionUtils { export function MakeMultiTouchTarget( element: HTMLElement, - startFunc: (e: Event, me: MultiTouchEvent) => void, + startFunc: (e: Event, me: MultiTouchEvent) => void ): MultiTouchEventDisposer { const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent>).detail); // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent>).detail) : undefined; @@ -46,6 +46,17 @@ export namespace InteractionUtils { }; } + export function MakeHoldTouchTarget( + element: HTMLElement, + func: (e: Event, me: MultiTouchEvent) => void + ): MultiTouchEventDisposer { + const handler = (e: Event) => func(e, (e as CustomEvent>).detail); + element.addEventListener("dashOnTouchHoldStart", handler); + return () => { + element.removeEventListener("dashOnTouchHoldStart", handler); + }; + } + export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { const myTouches = new Array(); for (const pt of mte.touches) { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 96c1513ff..0777bc2bc 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -32,6 +32,7 @@ export default class GestureOverlay extends Touchable { private _thumbX?: number; private _thumbY?: number; private thumbIdentifier?: number; + private _holdTimer: NodeJS.Timeout | undefined; private _hands: (React.Touch[])[] = []; protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -96,6 +97,7 @@ export default class GestureOverlay extends Touchable { ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); if (this.prevPoints.size && this.prevPoints.size < 5) { + const nts = this.getNewTouches(te); const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); target?.dispatchEvent( @@ -112,6 +114,28 @@ export default class GestureOverlay extends Touchable { } ) ); + if (this.prevPoints.size === 1 && this._holdTimer === undefined) { + console.log("started"); + this._holdTimer = setTimeout(() => { + console.log("hold"); + const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); + target?.dispatchEvent( + new CustomEvent>("dashOnTouchHoldStart", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: te + } + } + ) + ); + this._holdTimer = undefined; + }, (1000)); + } document.removeEventListener("touchmove", this.onReactTouchMove); document.removeEventListener("touchend", this.onReactTouchEnd); document.addEventListener("touchmove", this.onReactTouchMove); @@ -126,6 +150,10 @@ export default class GestureOverlay extends Touchable { onReactTouchMove = (e: TouchEvent) => { const nts: any = this.getNewTouches(e); + if (this.prevPoints.size === 1 && this._holdTimer) { + clearTimeout(this._holdTimer); + this._holdTimer = undefined; + } document.dispatchEvent( new CustomEvent>("dashOnTouchMove", { @@ -143,6 +171,10 @@ export default class GestureOverlay extends Touchable { onReactTouchEnd = (e: TouchEvent) => { const nts: any = this.getNewTouches(e); + if (this.prevPoints.size === 1 && this._holdTimer) { + clearTimeout(this._holdTimer); + this._holdTimer = undefined; + } document.dispatchEvent( new CustomEvent>("dashOnTouchEnd", { diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index eed72e3c5..da656dd57 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -8,7 +8,6 @@ const HOLD_DURATION = 1000; export abstract class Touchable extends React.Component { //private holdTimer: NodeJS.Timeout | undefined; - holdTimer: NodeJS.Timeout | undefined; private moveDisposer?: InteractionUtils.MultiTouchEventDisposer; private endDisposer?: InteractionUtils.MultiTouchEventDisposer; private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -29,6 +28,8 @@ export abstract class Touchable extends React.Component { */ @action protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { + console.log(e); + console.log(me); const actualPts: React.Touch[] = []; const te = me.touchEvent; // loop through all touches on screen @@ -68,10 +69,7 @@ export abstract class Touchable extends React.Component { // clearTimeout(this.holdTimer) // this.holdTimer = undefined; // } - console.log(this.holdTimer); - if (!this.holdTimer) { - this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e, me), HOLD_DURATION); - } + // console.log(this.holdTimer); // console.log(this.holdTimer); break; case 2: @@ -96,11 +94,6 @@ export abstract class Touchable extends React.Component { // if we're not actually moving a lot, don't consider it as dragging yet if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return; this._touchDrag = true; - if (this.holdTimer) { - console.log("CLEAR"); - clearTimeout(this.holdTimer); - // this.holdTimer = undefined; - } // console.log(myTouches.length); switch (myTouches.length) { case 1: @@ -123,6 +116,7 @@ export abstract class Touchable extends React.Component { @action protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { // console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up"); + console.log("end"); // remove all the touches associated with the event const te = me.touchEvent; for (const pt of me.changedTouches) { @@ -132,15 +126,6 @@ export abstract class Touchable extends React.Component { } } } - if (this.holdTimer) { - console.log(this.holdTimer); - clearTimeout(this.holdTimer); - console.log(this.holdTimer); - - this.holdTimer = undefined; - console.log(this.holdTimer); - console.log("clear"); - } this._touchDrag = false; te.stopPropagation(); @@ -187,7 +172,6 @@ export abstract class Touchable extends React.Component { handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); me.touchEvent.stopPropagation(); - this.holdTimer = undefined; this.removeMoveListeners(); this.removeEndListeners(); this.removeHoldMoveListeners(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ae2ee9e41..82cb36aa7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -104,6 +104,7 @@ export class DocumentView extends DocComponent(Docu private _titleRef = React.createRef(); protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + private holdDisposer?: InteractionUtils.MultiTouchEventDisposer; public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } @@ -120,6 +121,7 @@ export class DocumentView extends DocComponent(Docu handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { + console.log(me); console.log("S"); this.onRadialMenu(e, me); const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; @@ -164,6 +166,7 @@ export class DocumentView extends DocComponent(Docu this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); + this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); } @@ -173,14 +176,19 @@ export class DocumentView extends DocComponent(Docu this._dropDisposer && this._dropDisposer(); this._gestureEventDisposer && this._gestureEventDisposer(); this.multiTouchDisposer && this.multiTouchDisposer(); + this.holdDisposer && this.holdDisposer(); this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); + this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); } @action componentWillUnmount() { this._dropDisposer && this._dropDisposer(); + this._gestureEventDisposer && this._gestureEventDisposer(); + this.multiTouchDisposer && this.multiTouchDisposer(); + this.holdDisposer && this.holdDisposer(); Doc.UnBrushDoc(this.props.Document); !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } -- cgit v1.2.3-70-g09d2 From 962cfebaa2e15ac48aa969afc50c5913573623df Mon Sep 17 00:00:00 2001 From: ab Date: Wed, 29 Jan 2020 17:47:34 -0500 Subject: fixed menu --- package-lock.json | 35 +++++--------- src/client/views/GestureOverlay.tsx | 85 ++++++++++++++++++++++++++++----- src/client/views/Touchable.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 33 +++++++------ 4 files changed, 105 insertions(+), 52 deletions(-) (limited to 'src/client/views') diff --git a/package-lock.json b/package-lock.json index 4a5de1c66..40272b91a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5366,8 +5366,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -5404,8 +5403,7 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", @@ -5414,8 +5412,7 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -5518,8 +5515,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -5529,7 +5525,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5555,7 +5550,6 @@ "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5572,7 +5566,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5656,7 +5649,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5732,8 +5724,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -5763,7 +5754,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5781,7 +5771,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5820,13 +5809,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -7078,7 +7065,7 @@ "babel-runtime": "^6.22.0", "bluebird": "^3.4.7", "boolify-string": "^2.0.2", - "emit-logger": "github:chocolateboy/emit-logger#better-emitter-name", + "emit-logger": "github:chocolateboy/emit-logger#b9d25a2d939e42f29c940861e9648bd0fb810070", "lodash": "^4.17.4", "semver": "^5.0.3", "source-map-support": "^0.5.9" @@ -14174,7 +14161,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -15527,7 +15514,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -15557,7 +15544,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 0777bc2bc..029834725 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -50,21 +50,21 @@ export default class GestureOverlay extends Touchable { this._hands.forEach((hand) => { for (let i = 0; i < e.targetTouches.length; i++) { const pt = e.targetTouches.item(i); - if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + if (pt && hand.some((finger) => finger.screenX === pt ?.screenX && finger.screenY === pt.screenY)) { ntt.splice(ntt.indexOf(pt)); } } for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); - if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + if (pt && hand.some((finger) => finger.screenX === pt ?.screenX && finger.screenY === pt.screenY)) { nct.splice(nct.indexOf(pt)); } } for (let i = 0; i < e.touches.length; i++) { const pt = e.touches.item(i); - if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + if (pt && hand.some((finger) => finger.screenX === pt ?.screenX && finger.screenY === pt.screenY)) { nt.splice(ntt.indexOf(pt)); } } @@ -100,7 +100,7 @@ export default class GestureOverlay extends Touchable { const nts = this.getNewTouches(te); const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); - target?.dispatchEvent( + target ?.dispatchEvent( new CustomEvent>("dashOnTouchStart", { bubbles: true, @@ -119,7 +119,7 @@ export default class GestureOverlay extends Touchable { this._holdTimer = setTimeout(() => { console.log("hold"); const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); - target?.dispatchEvent( + target ?.dispatchEvent( new CustomEvent>("dashOnTouchHoldStart", { bubbles: true, @@ -134,6 +134,12 @@ export default class GestureOverlay extends Touchable { ) ); this._holdTimer = undefined; + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + document.removeEventListener("touchmove", this.onReactHoldTouchMove); + document.removeEventListener("touchend", this.onReactHoldTouchEnd); + document.addEventListener("touchmove", this.onReactHoldTouchMove); + document.addEventListener("touchend", this.onReactHoldTouchEnd); }, (1000)); } document.removeEventListener("touchmove", this.onReactTouchMove); @@ -148,6 +154,63 @@ export default class GestureOverlay extends Touchable { } } + onReactHoldTouchMove = (e: TouchEvent) => { + const nts: any = this.getNewTouches(e); + if (this.prevPoints.size === 1 && this._holdTimer) { + clearTimeout(this._holdTimer); + this._holdTimer = undefined; + } + document.dispatchEvent( + new CustomEvent>("dashOnTouchHoldMove", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: e + } + }) + ); + } + + onReactHoldTouchEnd = (e: TouchEvent) => { + const nts: any = this.getNewTouches(e); + if (this.prevPoints.size === 1 && this._holdTimer) { + clearTimeout(this._holdTimer); + this._holdTimer = undefined; + } + document.dispatchEvent( + new CustomEvent>("dashOnTouchHoldEnd", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: e + } + }) + ); + for (let i = 0; i < e.changedTouches.length; i++) { + const pt = e.changedTouches.item(i); + if (pt) { + if (this.prevPoints.has(pt.identifier)) { + this.prevPoints.delete(pt.identifier); + } + } + } + + if (this.prevPoints.size === 0) { + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + } + e.stopPropagation(); + } + + onReactTouchMove = (e: TouchEvent) => { const nts: any = this.getNewTouches(e); if (this.prevPoints.size === 1 && this._holdTimer) { @@ -211,7 +274,7 @@ export default class GestureOverlay extends Touchable { if (pt.radiusX > 1 && pt.radiusY > 1) { for (let j = 0; j < e.targetTouches.length; j++) { const tPt = e.targetTouches.item(j); - if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) { + if (tPt ?.screenX === pt ?.screenX && tPt ?.screenY === pt ?.screenY) { if (pt && this.prevPoints.has(pt.identifier)) { fingers.push(pt); } @@ -225,7 +288,7 @@ export default class GestureOverlay extends Touchable { this._thumbY = thumb.clientY; return; } - this.thumbIdentifier = thumb?.identifier; + this.thumbIdentifier = thumb ?.identifier; fingers.forEach((f) => this.prevPoints.delete(f.identifier)); this._hands.push(fingers); const others = fingers.filter(f => f !== thumb); @@ -325,8 +388,8 @@ export default class GestureOverlay extends Touchable { callbackFn: callback } }); - target1?.dispatchEvent(ge); - target2?.dispatchEvent(ge); + target1 ?.dispatchEvent(ge); + target2 ?.dispatchEvent(ge); return actionPerformed; } @@ -344,7 +407,7 @@ export default class GestureOverlay extends Touchable { switch (result.Name) { case GestureUtils.Gestures.Box: const target = document.elementFromPoint(this._points[0].X, this._points[0].Y); - target?.dispatchEvent(new CustomEvent("dashOnGesture", + target ?.dispatchEvent(new CustomEvent("dashOnGesture", { bubbles: true, detail: { @@ -369,7 +432,7 @@ export default class GestureOverlay extends Touchable { if (!actionPerformed) { const target = document.elementFromPoint(this._points[0].X, this._points[0].Y); - target?.dispatchEvent( + target ?.dispatchEvent( new CustomEvent("dashOnGesture", { bubbles: true, diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index da656dd57..13cc38589 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -28,8 +28,7 @@ export abstract class Touchable extends React.Component { */ @action protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - console.log(e); - console.log(me); + const actualPts: React.Touch[] = []; const te = me.touchEvent; // loop through all touches on screen @@ -116,7 +115,6 @@ export abstract class Touchable extends React.Component { @action protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { // console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up"); - console.log("end"); // remove all the touches associated with the event const te = me.touchEvent; for (const pt of me.changedTouches) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 82cb36aa7..fd7aad0d9 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -116,13 +116,13 @@ export class DocumentView extends DocComponent(Docu @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; } @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; } - private _firstX: number = 0; - private _firstY: number = 0; + private _firstX: number = -1; + private _firstY: number = -1; handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { - console.log(me); - console.log("S"); + this.addHoldMoveListeners(); + this.addHoldEndListeners(); this.onRadialMenu(e, me); const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; this._firstX = pt.pageX; @@ -131,16 +131,21 @@ export class DocumentView extends DocComponent(Docu } handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - console.log("K"); const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + console.log(pt.pageX, this._firstX, pt.pageY, this._firstY); + if (this._firstX === -1 || this._firstY === -1) { + return; + } if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { + console.log("WHY"); this.handle1PointerHoldEnd(e, me); } } handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - console.log("E"); RadialMenu.Instance.closeMenu(); + this._firstX = -1; + this._firstY = -1; } @action @@ -150,7 +155,7 @@ export class DocumentView extends DocComponent(Docu RadialMenu.Instance.openMenu(); RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 }); - RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView ?.removeDocument(this.props.Document), icon: "trash", selected: -1 }); RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 }); RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); @@ -229,13 +234,13 @@ export class DocumentView extends DocComponent(Docu e.preventDefault(); if (e.key === "†" || e.key === "t") { if (!StrCast(this.layoutDoc.showTitle)) this.layoutDoc.showTitle = "title"; - if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0); + if (!this._titleRef.current) setTimeout(() => this._titleRef.current ?.setIsFocused(true), 0); else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text... { - this._titleRef.current?.setIsFocused(false); - const any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any); + this._titleRef.current ?.setIsFocused(false); + const any = (this._mainCont.current ?.getElementsByClassName("ProseMirror") ?.[0] as any); any.keeplocation = true; - any?.focus(); + any ?.focus(); } } } else if (e.key === "f") { @@ -251,7 +256,7 @@ export class DocumentView extends DocComponent(Docu (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { e.stopPropagation(); let preventDefault = true; - if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click + if (this._doubleTap && this.props.renderDepth && !this.onClickHandler ?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click const fullScreenAlias = Doc.MakeAlias(this.props.Document); if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias.layoutCustom !== undefined) { fullScreenAlias.layoutKey = "layoutCustom"; @@ -265,7 +270,7 @@ export class DocumentView extends DocComponent(Docu ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY); } else if (this.props.Document.isButton === "Selector") { // this should be moved to an OnClick script FormattedTextBoxComment.Hide(); - this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0]!, this.props.Document)])); + this.Document.links ?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0]!, this.props.Document)])); } else if (this.Document.isButton) { SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered. this.buttonClick(e.altKey, e.ctrlKey); @@ -638,7 +643,7 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action setCustomView = (custom: boolean): void => { - if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) { + if (this.props.ContainingCollectionView ?.props.DataDoc || this.props.ContainingCollectionView ?.props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document); } else { custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document); -- cgit v1.2.3-70-g09d2 From eb1ce559cefedd3666a57ea00594013fbc3b3692 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Fri, 31 Jan 2020 14:23:59 -0500 Subject: pull --- src/client/cognitive_services/CognitiveServices.ts | 10 ++++++++- src/client/views/GestureOverlay.tsx | 24 +++++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) (limited to 'src/client/views') diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 9e2ceac62..62308f056 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -47,7 +47,8 @@ export namespace CognitiveServices { let results: any; try { results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json)); - } catch { + } catch (e) { + throw e; results = undefined; } return results; @@ -193,6 +194,13 @@ export namespace CognitiveServices { batch.end(); }; + export const InterpretStrokes = async (strokes: InkData[]) => { + let results = await ExecuteQuery(Service.Handwriting, Manager, strokes); + if (results) { + results.recognitionUnits && (results = results.recognitionUnits); + } + return results; + } } export interface AzureStrokeData { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 580c53a37..e25647e69 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -6,7 +6,7 @@ import { computed, observable, action, runInAction, IReactionDisposer, reaction import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { InteractionUtils } from "../util/InteractionUtils"; import { InkingControl } from "./InkingControl"; -import { InkTool } from "../../new_fields/InkField"; +import { InkTool, InkData } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { LinkManager } from "../util/LinkManager"; import { DocUtils } from "../documents/Documents"; @@ -19,6 +19,7 @@ import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyStr import { DocumentView } from "./nodes/DocumentView"; import { Transform } from "../util/Transform"; import { DocumentContentsView } from "./nodes/DocumentContentsView"; +import { CognitiveServices } from "../cognitive_services/CognitiveServices"; @observer export default class GestureOverlay extends Touchable { @@ -34,6 +35,7 @@ export default class GestureOverlay extends Touchable { @observable private _thumbY?: number; @observable private _pointerY?: number; @observable private _points: { X: number, Y: number }[] = []; + @observable private _strokes: InkData[] = []; @observable private _palette?: JSX.Element; @observable private _clipboardDoc?: JSX.Element; @@ -364,7 +366,10 @@ export default class GestureOverlay extends Touchable { if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { switch (this.Tool) { case ToolglassTools.InkToText: - break; + this._strokes.push(this._points); + this._points = []; + console.log(CognitiveServices.Inking.Appliers.InterpretStrokes([this._points])); + return; } } else { @@ -420,8 +425,10 @@ export default class GestureOverlay extends Touchable { } @computed get svgBounds() { - const xs = this._points.map(p => p.X); - const ys = this._points.map(p => p.Y); + const sxs = this._strokes.reduce((acc, curr) => acc.concat(...curr.map(p => p.X)), new Array()); + const xs = this._points.map(p => p.X).concat(sxs); + const sys = this._strokes.reduce((acc, curr) => acc.concat(...curr.map(p => p.Y)), new Array()); + const ys = this._points.map(p => p.Y).concat(sys); const right = Math.max(...xs); const left = Math.min(...xs); const bottom = Math.max(...ys); @@ -429,8 +436,8 @@ export default class GestureOverlay extends Touchable { return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top }; } - @computed get currentStroke() { - if (this._points.length <= 1) { + @computed get currentStrokes() { + if (this._points.length <= 1 && this._strokes.length <= 1) { return (null); } @@ -438,6 +445,7 @@ export default class GestureOverlay extends Touchable { return ( + {this._strokes.map(l => InteractionUtils.CreatePolyline(l, B.left, B.top, this.Color, this.Width))} {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} ); @@ -447,7 +455,7 @@ export default class GestureOverlay extends Touchable { return [ this.props.children, this._palette, - this.currentStroke + this.currentStrokes ]; } @@ -511,6 +519,8 @@ export default class GestureOverlay extends Touchable { } } +// export class + export enum ToolglassTools { InkToText = "inktotext", None = "none", -- cgit v1.2.3-70-g09d2 From ecf0f5b8f426db9e66c05e759f61294811b15fca Mon Sep 17 00:00:00 2001 From: vellichora Date: Sat, 1 Feb 2020 14:56:19 -0500 Subject: mobile ink overlay is draggable from desktop --- src/client/DocServer.ts | 19 ++- src/client/util/InteractionUtils.ts | 165 ------------------- src/client/util/InteractionUtils.tsx | 182 +++++++++++++++++++++ src/client/views/GestureOverlay.scss | 4 - src/client/views/GestureOverlay.tsx | 25 +-- src/client/views/InkingStroke.tsx | 17 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 1 + src/mobile/MobileInkOverlay.scss | 38 +++++ src/mobile/MobileInkOverlay.tsx | 91 +++++++++-- src/mobile/MobileInterface.scss | 2 +- src/mobile/MobileInterface.tsx | 90 ++++++---- src/server/Message.ts | 14 +- src/server/Websocket/Websocket.ts | 8 +- 14 files changed, 393 insertions(+), 264 deletions(-) delete mode 100644 src/client/util/InteractionUtils.ts create mode 100644 src/client/util/InteractionUtils.tsx create mode 100644 src/mobile/MobileInkOverlay.scss (limited to 'src/client/views') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index a1cb42df2..b20cd3521 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,5 +1,5 @@ import * as OpenSocket from 'socket.io-client'; -import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkBoxContent } from "./../server/Message"; +import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPosition } from "./../server/Message"; import { Opt, Doc } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; @@ -72,9 +72,13 @@ export namespace DocServer { Utils.Emit(_socket, MessageStore.GesturePoints, content); } - export function dispatchBoxTrigger(content: MobileInkBoxContent) { + export function dispatchOverlayTrigger(content: MobileInkOverlayContent) { // _socket.emit("dispatchBoxTrigger"); - Utils.Emit(_socket, MessageStore.MobileInkBoxTrigger, content); + Utils.Emit(_socket, MessageStore.MobileInkOverlayTrigger, content); + } + + export function dispatchOverlayPositionUpdate(content: UpdateMobileInkOverlayPosition) { + Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content); } } @@ -100,13 +104,18 @@ export namespace DocServer { Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, () => { alert("Your connection to the server has been terminated."); }); + + // mobile ink overlay socket events to communicate between mobile view and desktop view _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => { MobileInkOverlay.Instance.drawStroke(content); }); - _socket.addEventListener("receiveBoxTrigger", (content: MobileInkBoxContent) => { - GestureOverlay.Instance.enableMobileInkBox(content); + _socket.addEventListener("receiveOverlayTrigger", (content: MobileInkOverlayContent) => { + GestureOverlay.Instance.enableMobileInkOverlay(content); MobileInkOverlay.Instance.initMobileInkOverlay(content); }); + _socket.addEventListener("updateMobileOverlayPosition", (content: UpdateMobileInkOverlayPosition) => { + MobileInkOverlay.Instance.updatePosition(content); + }); } function errorFunc(): never { diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts deleted file mode 100644 index 76b43da3c..000000000 --- a/src/client/util/InteractionUtils.ts +++ /dev/null @@ -1,165 +0,0 @@ -export namespace InteractionUtils { - export const MOUSETYPE = "mouse"; - export const TOUCHTYPE = "touch"; - export const PENTYPE = "pen"; - export const ERASERTYPE = "eraser"; - - const POINTER_PEN_BUTTON = -1; - const REACT_POINTER_PEN_BUTTON = 0; - const ERASER_BUTTON = 5; - - export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { - const myTouches = new Array(); - for (let i = 0; i < e.targetTouches.length; i++) { - const pt: any = e.targetTouches.item(i); - if (pt && prevPoints.has(pt.identifier)) { - if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { - myTouches.push(pt); - } - } - } - return myTouches; - } - - export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean { - switch (type) { - // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2 - case PENTYPE: - return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0); - case ERASERTYPE: - return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); - default: - return e.pointerType === type; - } - } - - export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number { - return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2)); - } - - /** - * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point) - * @param pts - n-arbitrary long list of points - */ - export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } { - const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length; - const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length; - return { X: centerX, Y: centerY }; - } - - /** - * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in - * @param pt1 - new point that corresponds to oldPoint1 - * @param pt2 - new point that corresponds to oldPoint2 - * @param oldPoint1 - previous point 1 - * @param oldPoint2 - previous point 2 - */ - export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { - const threshold = 4; - const oldDist = TwoPointEuclidist(oldPoint1, oldPoint2); - const newDist = TwoPointEuclidist(pt1, pt2); - - /** if they have the same sign, then we are either pinching in or out. - * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch) - * so that it can still pan without freaking out - */ - if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) { - return Math.sign(oldDist - newDist); - } - return 0; - } - - /** - * Returns -1 if pinning and pinching out, 0 if not pinning, and 1 if pinching in - * @param pt1 - new point that corresponds to oldPoint1 - * @param pt2 - new point that corresponds to oldPoint2 - * @param oldPoint1 - previous point 1 - * @param oldPoint2 - previous point 2 - */ - export function Pinning(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { - const threshold = 4; - - const pt1Dist = TwoPointEuclidist(oldPoint1, pt1); - const pt2Dist = TwoPointEuclidist(oldPoint2, pt2); - - const pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2); - - if (pinching !== 0) { - if ((pt1Dist < threshold && pt2Dist > threshold) || (pt1Dist > threshold && pt2Dist < threshold)) { - return pinching; - } - } - return 0; - } - - export function IsDragging(oldTouches: Map, newTouches: React.Touch[], leniency: number): boolean { - for (const touch of newTouches) { - if (touch) { - const oldTouch = oldTouches.get(touch.identifier); - if (oldTouch) { - if (TwoPointEuclidist(touch, oldTouch) >= leniency) { - return true; - } - } - } - } - return false; - } - - // These might not be very useful anymore, but I'll leave them here for now -syip2 - { - - - /** - * Returns the type of Touch Interaction from a list of points. - * Also returns any data that is associated with a Touch Interaction - * @param pts - List of points - */ - // export function InterpretPointers(pts: React.Touch[]): { type: Opt, data?: any } { - // const leniency = 200; - // switch (pts.length) { - // case 1: - // return { type: OneFinger }; - // case 2: - // return { type: TwoSeperateFingers }; - // case 3: - // let pt1 = pts[0]; - // let pt2 = pts[1]; - // let pt3 = pts[2]; - // if (pt1 && pt2 && pt3) { - // let dist12 = TwoPointEuclidist(pt1, pt2); - // let dist23 = TwoPointEuclidist(pt2, pt3); - // let dist13 = TwoPointEuclidist(pt1, pt3); - // console.log(`distances: ${dist12}, ${dist23}, ${dist13}`); - // let dist12close = dist12 < leniency; - // let dist23close = dist23 < leniency; - // let dist13close = dist13 < leniency; - // let xor2313 = dist23close ? !dist13close : dist13close; - // let xor = dist12close ? !xor2313 : xor2313; - // // three input xor because javascript doesn't have logical xor's - // if (xor) { - // let points: number[] = []; - // let min = Math.min(dist12, dist23, dist13); - // switch (min) { - // case dist12: - // points = [0, 1, 2]; - // break; - // case dist23: - // points = [1, 2, 0]; - // break; - // case dist13: - // points = [0, 2, 1]; - // break; - // } - // return { type: TwoToOneFingers, data: points }; - // } - // else { - // return { type: ThreeSeperateFingers, data: null }; - // } - // } - // default: - // return { type: undefined }; - // } - // } - } -} \ No newline at end of file diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx new file mode 100644 index 000000000..34c7cfa74 --- /dev/null +++ b/src/client/util/InteractionUtils.tsx @@ -0,0 +1,182 @@ +import React = require("react"); + +export namespace InteractionUtils { + export const MOUSETYPE = "mouse"; + export const TOUCHTYPE = "touch"; + export const PENTYPE = "pen"; + export const ERASERTYPE = "eraser"; + + const POINTER_PEN_BUTTON = -1; + const REACT_POINTER_PEN_BUTTON = 0; + const ERASER_BUTTON = 5; + + export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { + const myTouches = new Array(); + for (let i = 0; i < e.targetTouches.length; i++) { + const pt: any = e.targetTouches.item(i); + if (pt && prevPoints.has(pt.identifier)) { + if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { + myTouches.push(pt); + } + } + } + return myTouches; + } + + // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view + export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) { + const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); + return ( + + ); + } + + export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean { + switch (type) { + // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2 + case PENTYPE: + return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0); + case ERASERTYPE: + return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); + default: + return e.pointerType === type; + } + } + + export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number { + return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2)); + } + + /** + * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point) + * @param pts - n-arbitrary long list of points + */ + export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } { + const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length; + const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length; + return { X: centerX, Y: centerY }; + } + + /** + * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in + * @param pt1 - new point that corresponds to oldPoint1 + * @param pt2 - new point that corresponds to oldPoint2 + * @param oldPoint1 - previous point 1 + * @param oldPoint2 - previous point 2 + */ + export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { + const threshold = 4; + const oldDist = TwoPointEuclidist(oldPoint1, oldPoint2); + const newDist = TwoPointEuclidist(pt1, pt2); + + /** if they have the same sign, then we are either pinching in or out. + * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch) + * so that it can still pan without freaking out + */ + if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) { + return Math.sign(oldDist - newDist); + } + return 0; + } + + /** + * Returns -1 if pinning and pinching out, 0 if not pinning, and 1 if pinching in + * @param pt1 - new point that corresponds to oldPoint1 + * @param pt2 - new point that corresponds to oldPoint2 + * @param oldPoint1 - previous point 1 + * @param oldPoint2 - previous point 2 + */ + export function Pinning(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { + const threshold = 4; + + const pt1Dist = TwoPointEuclidist(oldPoint1, pt1); + const pt2Dist = TwoPointEuclidist(oldPoint2, pt2); + + const pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2); + + if (pinching !== 0) { + if ((pt1Dist < threshold && pt2Dist > threshold) || (pt1Dist > threshold && pt2Dist < threshold)) { + return pinching; + } + } + return 0; + } + + export function IsDragging(oldTouches: Map, newTouches: React.Touch[], leniency: number): boolean { + for (const touch of newTouches) { + if (touch) { + const oldTouch = oldTouches.get(touch.identifier); + if (oldTouch) { + if (TwoPointEuclidist(touch, oldTouch) >= leniency) { + return true; + } + } + } + } + return false; + } + + // These might not be very useful anymore, but I'll leave them here for now -syip2 + { + + + /** + * Returns the type of Touch Interaction from a list of points. + * Also returns any data that is associated with a Touch Interaction + * @param pts - List of points + */ + // export function InterpretPointers(pts: React.Touch[]): { type: Opt, data?: any } { + // const leniency = 200; + // switch (pts.length) { + // case 1: + // return { type: OneFinger }; + // case 2: + // return { type: TwoSeperateFingers }; + // case 3: + // let pt1 = pts[0]; + // let pt2 = pts[1]; + // let pt3 = pts[2]; + // if (pt1 && pt2 && pt3) { + // let dist12 = TwoPointEuclidist(pt1, pt2); + // let dist23 = TwoPointEuclidist(pt2, pt3); + // let dist13 = TwoPointEuclidist(pt1, pt3); + // console.log(`distances: ${dist12}, ${dist23}, ${dist13}`); + // let dist12close = dist12 < leniency; + // let dist23close = dist23 < leniency; + // let dist13close = dist13 < leniency; + // let xor2313 = dist23close ? !dist13close : dist13close; + // let xor = dist12close ? !xor2313 : xor2313; + // // three input xor because javascript doesn't have logical xor's + // if (xor) { + // let points: number[] = []; + // let min = Math.min(dist12, dist23, dist13); + // switch (min) { + // case dist12: + // points = [0, 1, 2]; + // break; + // case dist23: + // points = [1, 2, 0]; + // break; + // case dist13: + // points = [0, 2, 1]; + // break; + // } + // return { type: TwoToOneFingers, data: points }; + // } + // else { + // return { type: ThreeSeperateFingers, data: null }; + // } + // } + // default: + // return { type: undefined }; + // } + // } + } +} \ No newline at end of file diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index 2996b7073..31601efd4 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -5,8 +5,4 @@ top: 0; left: 0; touch-action: none; -} - -.mobileInkOverlay { - border: 5px dashed red; } \ No newline at end of file diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 81284b543..a01a86b53 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -19,7 +19,7 @@ import Palette from "./Palette"; import MobileInterface from "../../mobile/MobileInterface"; import { MainView } from "./MainView"; import { DocServer } from "../DocServer"; -import { GestureContent, MobileInkBoxContent } from "../../server/Message"; +import { GestureContent, MobileInkOverlayContent } from "../../server/Message"; import { Point } from "../northstar/model/idea/idea"; import MobileInkOverlay from "../../mobile/MobileInkOverlay"; @@ -217,21 +217,6 @@ export default class GestureOverlay extends Touchable { return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top }; } - // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view - CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color?: string, width?: number) { - const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); - return ( - - ); - } - @computed get currentStroke() { if (this._points.length <= 1) { return (null); @@ -241,23 +226,23 @@ export default class GestureOverlay extends Touchable { return ( - {this.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} + {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} ); } @action - enableMobileInkBox = (content: MobileInkBoxContent) => { - this.showMobileInkOverlay = content.enableBox; + enableMobileInkOverlay = (content: MobileInkOverlayContent) => { + this.showMobileInkOverlay = content.enableOverlay; } render() { return (
+ {this.showMobileInkOverlay ? : <>} {this.currentStroke} {this.props.children} {this._palette} - {this.showMobileInkOverlay ? : <>}
); } } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 8b346d5d9..aca507147 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -10,24 +10,11 @@ import "./InkingStroke.scss"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; import React = require("react"); import { TraceMobx } from "../../new_fields/util"; +import { InteractionUtils } from "../util/InteractionUtils"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); -export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color?: string, width?: number) { - const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); - return ( - - ); -} - @observer export class InkingStroke extends DocExtendableComponent(InkDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } @@ -44,7 +31,7 @@ export class InkingStroke extends DocExtendableComponent(Docu if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) { if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { e.stopPropagation(); + // TODO: check here for panning/inking } return; } diff --git a/src/mobile/MobileInkOverlay.scss b/src/mobile/MobileInkOverlay.scss new file mode 100644 index 000000000..2e45d0441 --- /dev/null +++ b/src/mobile/MobileInkOverlay.scss @@ -0,0 +1,38 @@ +.mobileInkOverlay { + border: 5px dashed red; +} + +.mobileInkOverlay-border { + background-color: rgba(0, 255, 0, .4); + position: absolute; + pointer-events: auto; + cursor: pointer; + + &.top { + width: calc(100% + 20px); + height: 10px; + top: -10px; + left: -10px; + } + + &.left { + width: 10px; + height: calc(100% + 20px); + top: -10px; + left: -10px; + } + + &.right { + width: 10px; + height: calc(100% + 20px); + top: -10px; + right: -10px; + } + + &.bottom { + width: calc(100% + 20px); + height: 10px; + bottom: -10px; + left: -10px; + } +} \ No newline at end of file diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx index 71dd20c51..5efc7b83a 100644 --- a/src/mobile/MobileInkOverlay.tsx +++ b/src/mobile/MobileInkOverlay.tsx @@ -1,8 +1,9 @@ import React = require('react'); import { observer } from "mobx-react"; -import { MobileInkBoxContent, GestureContent } from "../server/Message"; +import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPosition } from "../server/Message"; import { observable, action } from "mobx"; import { GestureUtils } from "../pen-gestures/GestureUtils"; +import "./MobileInkOverlay.scss"; @observer @@ -15,6 +16,11 @@ export default class MobileInkOverlay extends React.Component { @observable private _x: number = -300; @observable private _y: number = -300; + @observable private _offsetX: number = 0; + @observable private _offsetY: number = 0; + @observable private _isDragging: boolean = false; + private _mainCont: React.RefObject = React.createRef(); + constructor(props: Readonly<{}>) { super(props); MobileInkOverlay.Instance = this; @@ -28,18 +34,27 @@ export default class MobileInkOverlay extends React.Component { } @action - initMobileInkOverlay(content: MobileInkBoxContent) { + initMobileInkOverlay(content: MobileInkOverlayContent) { const { width, height } = content; const scaledSize = this.initialSize(width ? width : 0, height ? height : 0); - this._width = scaledSize.width; - this._height = scaledSize.height; - this._scale = scaledSize.scale; + this._width = scaledSize.width * .5; + this._height = scaledSize.height * .5; + this._scale = .5; //scaledSize.scale; this._x = 300; // TODO: center on screen this._y = 25; // TODO: center on screen } + @action + updatePosition(content: UpdateMobileInkOverlayPosition) { + const { dx, dy, dsize } = content; + console.log(dx, dy, dsize); + } + drawStroke = (content: GestureContent) => { + // TODO: figure out why strokes drawn in corner of mobile interface dont get inserted + const { points, bounds } = content; + console.log("received points", points, bounds); const B = { right: (bounds.right * this._scale) + this._x, @@ -65,16 +80,66 @@ export default class MobileInkOverlay extends React.Component { ); } + @action + dragStart = (e: React.PointerEvent) => { + console.log("pointer down"); + document.removeEventListener("pointermove", this.dragging); + document.removeEventListener("pointerup", this.dragEnd); + document.addEventListener("pointermove", this.dragging); + document.addEventListener("pointerup", this.dragEnd); + + this._isDragging = true; + this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left; + this._offsetY = e.pageY - this._mainCont.current!.getBoundingClientRect().top; + + e.preventDefault(); + e.stopPropagation(); + } + + @action + dragging = (e: PointerEvent) => { + const x = e.pageX - this._offsetX; + const y = e.pageY - this._offsetY; + + // TODO: don't allow drag over library? + this._x = Math.min(Math.max(x, 0), window.innerWidth - this._width); + this._y = Math.min(Math.max(y, 0), window.innerHeight - this._height); + + e.preventDefault(); + e.stopPropagation(); + } + + @action + dragEnd = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.dragging); + document.removeEventListener("pointerup", this.dragEnd); + + this._isDragging = false; + + e.preventDefault(); + e.stopPropagation(); + } + render() { + return ( -
+
+
+
+
+
+
); } } \ No newline at end of file diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss index e4cc919a5..8abe5a40d 100644 --- a/src/mobile/MobileInterface.scss +++ b/src/mobile/MobileInterface.scss @@ -1,4 +1,4 @@ -.mobileInterface-topButtons { +.mobileInterface-inkInterfaceButtons { position: absolute; display: flex; justify-content: space-between; diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 4840ea374..b191b3afb 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -10,7 +10,7 @@ import { DocumentView } from '../client/views/nodes/DocumentView'; import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils'; import { Transform } from '../client/util/Transform'; import { library } from '@fortawesome/fontawesome-svg-core'; -import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck } from '@fortawesome/free-solid-svg-icons'; +import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck, faLongArrowAltLeft } from '@fortawesome/free-solid-svg-icons'; import { Scripting } from '../client/util/Scripting'; import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView'; import GestureOverlay from '../client/views/GestureOverlay'; @@ -23,7 +23,7 @@ import { DateField } from '../new_fields/DateField'; import { GestureUtils } from '../pen-gestures/GestureUtils'; import { DocServer } from '../client/DocServer'; -library.add(faTrash, faCheck); +library.add(faLongArrowAltLeft); @observer export default class MobileInterface extends React.Component { @@ -68,8 +68,8 @@ export default class MobileInterface extends React.Component { InkingControl.Instance.switchTool(InkTool.Pen); this.drawingInk = true; - DocServer.Mobile.dispatchBoxTrigger({ - enableBox: true, + DocServer.Mobile.dispatchOverlayTrigger({ + enableOverlay: true, width: window.innerWidth, height: window.innerHeight }); @@ -112,12 +112,12 @@ export default class MobileInterface extends React.Component { return "hello"; } - onClick = (e: React.MouseEvent) => { + onBack = (e: React.MouseEvent) => { this.switchCurrentView("main"); InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool - DocServer.Mobile.dispatchBoxTrigger({ - enableBox: false, + DocServer.Mobile.dispatchOverlayTrigger({ + enableOverlay: false, width: window.innerWidth, height: window.innerHeight }); @@ -126,37 +126,61 @@ export default class MobileInterface extends React.Component { this.drawingInk = false; } + shiftLeft = (e: React.MouseEvent) => { + DocServer.Mobile.dispatchOverlayPositionUpdate({ + dx: -10 + }); + } + + shiftRight = (e: React.MouseEvent) => { + DocServer.Mobile.dispatchOverlayPositionUpdate({ + dx: 10 + }); + } + @computed get inkContent() { + // TODO: support panning and zooming + // TODO: handle moving of ink strokes if (this.mainContainer) { return ( - -
- - +
+
+
+ +
+
+ +
+
+ + +
- window.innerHeight} - PanelWidth={() => window.innerWidth} - focus={emptyFunction} - isSelected={returnFalse} - select={emptyFunction} - active={returnFalse} - ContentScaling={returnOne} - whenActiveChanged={returnFalse} - ScreenToLocalTransform={Transform.Identity} - ruleProvider={undefined} - renderDepth={0} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined}> - - + + window.innerHeight} + PanelWidth={() => window.innerWidth} + focus={emptyFunction} + isSelected={returnFalse} + select={emptyFunction} + active={returnFalse} + ContentScaling={returnOne} + whenActiveChanged={returnFalse} + ScreenToLocalTransform={Transform.Identity} + ruleProvider={undefined} + renderDepth={0} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined}> + + +
); } } diff --git a/src/server/Message.ts b/src/server/Message.ts index 1958286df..064a19653 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -50,12 +50,18 @@ export interface GestureContent { readonly color?: string; } -export interface MobileInkBoxContent { - readonly enableBox: boolean; +export interface MobileInkOverlayContent { + readonly enableOverlay: boolean; readonly width?: number; readonly height?: number; } +export interface UpdateMobileInkOverlayPosition { + readonly dx?: number; + readonly dy?: number; + readonly dsize?: number; +} + export namespace MessageStore { export const Foo = new Message("Foo"); export const Bar = new Message("Bar"); @@ -65,8 +71,10 @@ export namespace MessageStore { export const GetDocument = new Message("Get Document"); export const DeleteAll = new Message("Delete All"); export const ConnectionTerminated = new Message("Connection Terminated"); + export const GesturePoints = new Message("Gesture Points"); - export const MobileInkBoxTrigger = new Message("Trigger Mobile Ink Box"); + export const MobileInkOverlayTrigger = new Message("Trigger Mobile Ink Overlay"); + export const UpdateMobileInkOverlayPosition = new Message("Update Mobile Ink Overlay Position"); export const GetRefField = new Message("Get Ref Field"); export const GetRefFields = new Message("Get Ref Fields"); diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index 16e34bdfc..fe253400c 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -1,5 +1,5 @@ import { Utils } from "../../Utils"; -import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkBoxContent } from "../Message"; +import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent } from "../Message"; import { Client } from "../Client"; import { Socket } from "socket.io"; import { Database } from "../database"; @@ -55,7 +55,7 @@ export namespace WebSocket { Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id)); Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids)); Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content)); - Utils.AddServerHandler(socket, MessageStore.MobileInkBoxTrigger, content => processBoxTrigger(socket, content)); + Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content)); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); @@ -74,8 +74,8 @@ export namespace WebSocket { socket.broadcast.emit("receiveGesturePoints", content); } - function processBoxTrigger(socket: Socket, content: MobileInkBoxContent) { - socket.broadcast.emit("receiveBoxTrigger", content); + function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) { + socket.broadcast.emit("receiveOverlayTrigger", content); } function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) { -- cgit v1.2.3-70-g09d2 From 8775ff05e1cfd8f158fb6f0a973d3ee5b9284005 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Sat, 1 Feb 2020 17:50:38 -0500 Subject: ok finally ink reco is working lol --- src/client/views/GestureOverlay.tsx | 95 +++++++------------------------------ 1 file changed, 18 insertions(+), 77 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index c0e45d36f..284224fb2 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -312,11 +312,6 @@ export default class GestureOverlay extends Touchable { onPointerDown = (e: React.PointerEvent) => { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) { this._points.push({ X: e.clientX, Y: e.clientY }); - const canvas = this._canvas.current; - const ctx = canvas?.getContext("2d"); - if (canvas && ctx) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - } e.stopPropagation(); e.preventDefault(); @@ -368,19 +363,6 @@ export default class GestureOverlay extends Touchable { return actionPerformed; } - draw = (points: InkData) => { - let ctx; - if (this._canvas.current && this._canvas.current.getContext("2d") && (ctx = this._canvas.current.getContext("2d"))) { - ctx.strokeStyle = this.Color; - ctx.lineWidth = this.Width; - ctx.moveTo(points[0].X, points[0].Y); - for (let i = 1; i < points.length; i++) { - ctx.lineTo(points[i].X, points[i].Y); - } - ctx.stroke(); - } - } - @action onPointerUp = async (e: PointerEvent) => { if (this._points.length > 1) { @@ -394,52 +376,19 @@ export default class GestureOverlay extends Touchable { if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { switch (this.Tool) { case ToolglassTools.InkToText: - // this.draw(points); - // const dataUrl = this._canvas.current?.toDataURL("image/jpeg"); - // if (dataUrl) { - // DocServer.analyzeImage(dataUrl, (result: any) => { - // console.log("something"); - // console.log(result); - // }); - // } - - // htmlToImage.toCanvas(this._svg.current!).then((blob) => { - // if (blob) { - // console.log("blobbed"); - // blob.toDataURL("image/jpeg") - // const t = blob.getContext("2d")?.getImageData(0, 0, blob.width, blob.height); - // if (t) { - // DocServer.analyzeImage(t.data, (text: any) => { - // console.log(text); - // }); - // } - // } - // else { - // console.log("no blob") - // } - // runInAction(() => { - // this._strokes.push(this._points); - // this._points = []; - // }); - // }).catch(e => console.log(e)); - runInAction(() => { - this._strokes.push(this._points); - this._points = []; - }); + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this._strokes.push(new Array(...this._points)); + this._points = []; const results = await CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes); - // console.log(results); - const wordResults = results.filter(r => r.category === "inkWord"); - console.log(wordResults); + const wordResults = results.filter((r: any) => r.category === "inkWord"); const possibilities = [wordResults[0]?.recognizedText]; - possibilities.push(...wordResults[0].alternates?.map(a => a.recognizedString)); + possibilities.push(...wordResults[0]?.alternates?.map((a: any) => a.recognizedString)); console.log(possibilities); - - // return; break; case ToolglassTools.IgnoreGesture: this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; - this._canvas.current?.getContext("2d")?.restore(); break; } } @@ -461,14 +410,12 @@ export default class GestureOverlay extends Touchable { } if (actionPerformed) { this._points = []; - this._canvas.current?.getContext("2d")?.restore(); } } if (!actionPerformed) { this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; - this._canvas.current?.getContext("2d")?.restore(); } } } @@ -485,7 +432,7 @@ export default class GestureOverlay extends Touchable { detail: { points: stroke ?? this._points, gesture: gesture, - bounds: this.svgBounds + bounds: this.getBounds(stroke ?? this._points) } } ) @@ -506,26 +453,20 @@ export default class GestureOverlay extends Touchable { return this.getBounds(this._points); } - private _canvas = React.createRef(); - private _svg = React.createRef(); - @computed get currentStrokes() { - if (this._points.length <= 1 && this._strokes.length <= 1) { - return (null); - } - const B = this.svgBounds; return ( -
- - {this._strokes.map(l => { - const b = this.getBounds(l); - InteractionUtils.CreatePolyline(l, b.left, b.top, this.Color, this.Width); - })} - {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} - -
+ [this._strokes.map(l => { + let b = this.getBounds(l); + console.log(b); + return + {InteractionUtils.CreatePolyline(l, b.left, b.top, this.Color, this.Width)} + ; + }), + this._points.length <= 1 ? (null) : + {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} + ] ); } @@ -575,6 +516,7 @@ export default class GestureOverlay extends Touchable { return (
{this.elements} +
-
); } -- cgit v1.2.3-70-g09d2 From dab8f5893ffb8fab05a46695b9d1a690d1171bca Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Sun, 2 Feb 2020 19:18:23 -0500 Subject: some more stuff --- src/client/documents/Documents.ts | 1 + src/client/views/GestureOverlay.scss | 19 ++++ src/client/views/GestureOverlay.tsx | 111 ++++++++++++++++++--- src/client/views/Touchable.tsx | 2 - .../views/collections/CollectionLinearView.tsx | 3 +- .../authentication/models/current_user_utils.ts | 4 +- 6 files changed, 122 insertions(+), 18 deletions(-) (limited to 'src/client/views') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fa5707288..64583ae55 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -137,6 +137,7 @@ export interface DocumentOptions { isExpanded?: boolean; // is linear view expanded textTransform?: string; // is linear view expanded letterSpacing?: string; // is linear view expanded + flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse"; } class EmptyBox { diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index d980b0a91..60b53c528 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -13,6 +13,25 @@ height: 300px; } +.inkToTextDoc-cont { + position: absolute; + width: 300px; + height: 300px; + overflow: hidden; + + .inkToTextDoc-scroller { + overflow: visible; + + .collectionView { + overflow: visible; + + .collectionLinearView-outer { + overflow: visible; + } + } + } +} + .filter-cont { position: absolute; background-color: transparent; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 284224fb2..0c2e9e1bc 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -2,19 +2,19 @@ import React = require("react"); import { Touchable } from "./Touchable"; import { observer } from "mobx-react"; import "./GestureOverlay.scss"; -import { computed, observable, action, runInAction, IReactionDisposer, reaction } from "mobx"; +import { computed, observable, action, runInAction, IReactionDisposer, reaction, flow } from "mobx"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { InteractionUtils } from "../util/InteractionUtils"; import { InkingControl } from "./InkingControl"; import { InkTool, InkData } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { LinkManager } from "../util/LinkManager"; -import { DocUtils } from "../documents/Documents"; +import { DocUtils, Docs } from "../documents/Documents"; import { undoBatch } from "../util/UndoManager"; import { Scripting } from "../util/Scripting"; import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; -import Palette from "./Palette"; +import HorizontalPalette from "./Palette"; import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange } from "../../Utils"; import { DocumentView } from "./nodes/DocumentView"; import { Transform } from "../util/Transform"; @@ -22,6 +22,9 @@ import { DocumentContentsView } from "./nodes/DocumentContentsView"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; import { DocServer } from "../DocServer"; import htmlToImage from "html-to-image"; +import { ScriptField } from "../../new_fields/ScriptField"; +import { listSpec } from "../../new_fields/Schema"; +import { List } from "../../new_fields/List"; @observer export default class GestureOverlay extends Touchable { @@ -40,11 +43,13 @@ export default class GestureOverlay extends Touchable { @observable private _strokes: InkData[] = []; @observable private _palette?: JSX.Element; @observable private _clipboardDoc?: JSX.Element; + @observable private _possibilities: Doc[] = []; @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); } @computed private get showBounds() { return this.Tool !== ToolglassTools.None; } private _d1: Doc | undefined; + private _inkToTextDoc: Doc | undefined; private _thumbDoc: Doc | undefined; private thumbIdentifier?: number; private pointerIdentifier?: number; @@ -111,7 +116,6 @@ export default class GestureOverlay extends Touchable { ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); const nts = this.getNewTouches(te); - console.log(nts.nt.length); if (nts.nt.length < 5) { const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); @@ -239,10 +243,11 @@ export default class GestureOverlay extends Touchable { const thumbDoc = await Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc); if (thumbDoc) { runInAction(() => { + this._inkToTextDoc = FieldValue(Cast(thumbDoc.inkToTextDoc, Doc)); this._thumbDoc = thumbDoc; this._thumbX = thumb.clientX; this._thumbY = thumb.clientY; - this._palette = ; + this._palette = ; }); } @@ -280,10 +285,18 @@ export default class GestureOverlay extends Touchable { for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); - if (pt && pt.identifier === this.thumbIdentifier && this._thumbX && this._thumbDoc) { - if (Math.abs(pt.clientX - this._thumbX) > 20) { - this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); - this._thumbX = pt.clientX; + if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) { + if (this._thumbX && this._thumbDoc) { + if (Math.abs(pt.clientX - this._thumbX) > 20) { + this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); + this._thumbX = pt.clientX; + } + } + if (this._thumbY && this._inkToTextDoc) { + if (Math.abs(pt.clientY - this._thumbY) > 20) { + this._inkToTextDoc.selectedIndex = Math.max(0, NumCast(this._inkToTextDoc.selectedIndex) - Math.sign(pt.clientY - this._thumbY)); + this._thumbY = pt.clientY; + } } } if (pt && pt.identifier === this.pointerIdentifier) { @@ -304,6 +317,11 @@ export default class GestureOverlay extends Touchable { this.dispatchGesture(GestureUtils.Gestures.Stroke, s); }); this._strokes = []; + if (NumCast(this._inkToTextDoc?.selectedIndex) > 0) { + const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex) - 1]; + Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton.proto }, console.log); + } + this._possibilities = []; document.removeEventListener("touchend", this.handleHandUp); } } @@ -382,9 +400,27 @@ export default class GestureOverlay extends Touchable { this._points = []; const results = await CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes); const wordResults = results.filter((r: any) => r.category === "inkWord"); - const possibilities = [wordResults[0]?.recognizedText]; + const possibilities: string[] = []; + if (wordResults[0]?.recognizedText) { + possibilities.push(wordResults[0]?.recognizedText) + } possibilities.push(...wordResults[0]?.alternates?.map((a: any) => a.recognizedString)); - console.log(possibilities); + const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); + const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); + const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); + runInAction(() => { + console.log(possibilities); + const buttons = possibilities.map(p => Docs.Create.ButtonDocument({ + _height: r - l, _width: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t, + title: p, fontSize: 10, letterSpacing: "0px", textTransform: "unset", boxShadow: ".5px .5px 0px rgb(34, 34, 34)", + onClick: ScriptField.MakeScript('Docs.Create.TextDocument(this.title, {_width: 200, _height: 35, x: this.x, y: this.y})') + })); + if (this._inkToTextDoc) { + this._inkToTextDoc.data = new List(buttons); + this._inkToTextDoc.selectedIndex = 0; + } + this._possibilities = buttons; + }); break; case ToolglassTools.IgnoreGesture: this.dispatchGesture(GestureUtils.Gestures.Stroke); @@ -458,8 +494,7 @@ export default class GestureOverlay extends Touchable { return ( [this._strokes.map(l => { - let b = this.getBounds(l); - console.log(b); + const b = this.getBounds(l); return {InteractionUtils.CreatePolyline(l, b.left, b.top, this.Color, this.Width)} ; @@ -512,6 +547,53 @@ export default class GestureOverlay extends Touchable { this._clipboardDoc = undefined; } + @computed + private get inkToTextSuggestions() { + console.log(this._possibilities.length); + if (this._inkToTextDoc && this._possibilities.length) { + const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom)); + const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); + const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); + const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); + + return ( +
+
+ new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)} + ContentScaling={returnOne} + PanelWidth={() => 300} + PanelHeight={() => 300} + renderDepth={0} + backgroundColor={returnEmptyString} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + zoomToScale={emptyFunction} + getScale={returnOne} + />; +
+
+ ); + } + return null; + } + render() { return (
@@ -535,7 +617,8 @@ export default class GestureOverlay extends Touchable { display: this.showBounds ? "unset" : "none", }}>
-
); + {this.inkToTextSuggestions} +
); } } diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 7800c4019..ead6e2a00 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -92,7 +92,6 @@ export abstract class Touchable extends React.Component { if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return; this._touchDrag = true; if (this.holdTimer) { - console.log("CLEAR"); clearTimeout(this.holdTimer); // this.holdTimer = undefined; } @@ -129,7 +128,6 @@ export abstract class Touchable extends React.Component { } if (this.holdTimer) { clearTimeout(this.holdTimer); - console.log("clear"); } this._touchDrag = false; te.stopPropagation(); diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 67062ae41..e613bf411 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -82,13 +82,14 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { render() { const guid = Utils.GenerateGuid(); + const flexDir: any = StrCast(this.Document.flexDirection); return
this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} /> -
+
{this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => { const nested = pair.layout._viewType === CollectionViewType.Linear; const dref = React.createRef(); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index dbde351b3..6c37380f3 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -131,9 +131,11 @@ export class CurrentUserUtils { static setupThumbDoc(userDoc: Doc) { if (!userDoc.thumbDoc) { - userDoc.thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { + const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5, isExpanded: true, backgroundColor: "white" }); + thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" }); + userDoc.thumbDoc = thumbDoc; } return userDoc.thumbDoc; } -- cgit v1.2.3-70-g09d2 From 1007cfb325f2dcddc4365538e4b354d06eb85f2f Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Tue, 4 Feb 2020 18:12:47 -0500 Subject: ok so the toolglass is working, but it's super slow... i'll fix that later lol --- src/client/views/GestureOverlay.scss | 13 +- src/client/views/GestureOverlay.tsx | 151 ++++++++++++--------- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- src/pen-gestures/GestureUtils.ts | 11 +- .../authentication/models/current_user_utils.ts | 2 +- 5 files changed, 109 insertions(+), 74 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index 60b53c528..f425c438e 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -16,11 +16,13 @@ .inkToTextDoc-cont { position: absolute; width: 300px; - height: 300px; overflow: hidden; .inkToTextDoc-scroller { overflow: visible; + position: absolute; + width: 100%; + left: -24px; .collectionView { overflow: visible; @@ -30,6 +32,15 @@ } } } + + .shadow { + width: 100%; + height: calc(100% - 25px); + position: absolute; + top: 25px; + background-color: black; + opacity: 0.2; + } } .filter-cont { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 0c2e9e1bc..0fd6f3dba 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -2,7 +2,7 @@ import React = require("react"); import { Touchable } from "./Touchable"; import { observer } from "mobx-react"; import "./GestureOverlay.scss"; -import { computed, observable, action, runInAction, IReactionDisposer, reaction, flow } from "mobx"; +import { computed, observable, action, runInAction, IReactionDisposer, reaction, flow, trace } from "mobx"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { InteractionUtils } from "../util/InteractionUtils"; import { InkingControl } from "./InkingControl"; @@ -25,6 +25,8 @@ import htmlToImage from "html-to-image"; import { ScriptField } from "../../new_fields/ScriptField"; import { listSpec } from "../../new_fields/Schema"; import { List } from "../../new_fields/List"; +import { CollectionViewType } from "./collections/CollectionView"; +import InkCanvas from "./InkCanvas"; @observer export default class GestureOverlay extends Touchable { @@ -63,6 +65,11 @@ export default class GestureOverlay extends Touchable { GestureOverlay.Instance = this; } + componentDidMount = () => { + this._thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc)); + this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc)); + } + getNewTouches(e: React.TouchEvent | TouchEvent) { const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches); const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches); @@ -313,14 +320,23 @@ export default class GestureOverlay extends Touchable { this._palette = undefined; this.thumbIdentifier = undefined; this._thumbDoc = undefined; - this._strokes.forEach(s => { - this.dispatchGesture(GestureUtils.Gestures.Stroke, s); - }); - this._strokes = []; - if (NumCast(this._inkToTextDoc?.selectedIndex) > 0) { - const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex) - 1]; - Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton.proto }, console.log); + + let scriptWorked = false; + if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) { + const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex)]; + if (Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton }, console.log).success) { + scriptWorked = true; + console.log("success"); + }; + console.log(Cast(selectedButton?.proto?.onClick, ScriptField)?.script); + } + + if (!scriptWorked) { + this._strokes.forEach(s => { + this.dispatchGesture(GestureUtils.Gestures.Stroke, s); + }); } + this._strokes = []; this._possibilities = []; document.removeEventListener("touchend", this.handleHandUp); } @@ -409,14 +425,14 @@ export default class GestureOverlay extends Touchable { const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); runInAction(() => { - console.log(possibilities); const buttons = possibilities.map(p => Docs.Create.ButtonDocument({ - _height: r - l, _width: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t, + _height: 25, _width: r - l, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t, _viewType: CollectionViewType.Linear, + isExpanded: true, title: p, fontSize: 10, letterSpacing: "0px", textTransform: "unset", boxShadow: ".5px .5px 0px rgb(34, 34, 34)", - onClick: ScriptField.MakeScript('Docs.Create.TextDocument(this.title, {_width: 200, _height: 35, x: this.x, y: this.y})') + onClick: ScriptField.MakeScript('createText(this.proto.title, this.x, this.y)') })); - if (this._inkToTextDoc) { - this._inkToTextDoc.data = new List(buttons); + if (this._inkToTextDoc && this._thumbDoc) { + this._inkToTextDoc = this._thumbDoc.inkToTextDoc = Docs.Create.LinearDocument(buttons, { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" }); this._inkToTextDoc.selectedIndex = 0; } this._possibilities = buttons; @@ -459,7 +475,7 @@ export default class GestureOverlay extends Touchable { document.removeEventListener("pointerup", this.onPointerUp); } - dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData) => { + dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, data?: any) => { const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y); target?.dispatchEvent( new CustomEvent("dashOnGesture", @@ -468,7 +484,8 @@ export default class GestureOverlay extends Touchable { detail: { points: stroke ?? this._points, gesture: gesture, - bounds: this.getBounds(stroke ?? this._points) + bounds: this.getBounds(stroke ?? this._points), + text: data } } ) @@ -489,27 +506,21 @@ export default class GestureOverlay extends Touchable { return this.getBounds(this._points); } - @computed get currentStrokes() { + @computed get elements() { const B = this.svgBounds; + return [ + this.props.children, + this._palette, - return ( [this._strokes.map(l => { const b = this.getBounds(l); return - {InteractionUtils.CreatePolyline(l, b.left, b.top, this.Color, this.Width)} + {InteractionUtils.CreatePolyline(l, b.left, b.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)} ; }), this._points.length <= 1 ? (null) : - {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} + {InteractionUtils.CreatePolyline(this._points, B.left, B.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)} ] - ); - } - - @computed get elements() { - return [ - this.props.children, - this._palette, - this.currentStrokes ]; } @@ -548,53 +559,59 @@ export default class GestureOverlay extends Touchable { } @computed + private get suggestionContent() { + const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom)); + const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); + const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); + const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); + return ( +
+
+ new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)} + ContentScaling={returnOne} + PanelWidth={() => 300} + PanelHeight={() => 300} + renderDepth={0} + backgroundColor={returnEmptyString} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + zoomToScale={emptyFunction} + getScale={returnOne} + />; +
+
+
+
) + } + private get inkToTextSuggestions() { - console.log(this._possibilities.length); if (this._inkToTextDoc && this._possibilities.length) { - const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom)); - const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); - const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); - const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); - return ( -
-
- new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)} - ContentScaling={returnOne} - PanelWidth={() => 300} - PanelHeight={() => 300} - renderDepth={0} - backgroundColor={returnEmptyString} - focus={emptyFunction} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} - bringToFront={emptyFunction} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne} - />; -
-
+ this.suggestionContent ); } return null; } render() { + trace(); return (
{this.elements} @@ -647,4 +664,8 @@ Scripting.addGlobal(function resetPen() { GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(244, 67, 54)"; GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 5; }); +}); +Scripting.addGlobal(function createText(text: any, x: any, y: any) { + console.log("creating"); + GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text); }); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 41ef8c2a6..17139d9d9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -412,7 +412,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 })); sel.forEach(d => this.props.removeDocument(d)); break; - + case GestureUtils.Gestures.Text: + if (ge.text) { + const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y); + this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] })); + } } } diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 4b5ad6684..4e3493c1c 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -6,18 +6,16 @@ import { Doc, WidthSym, HeightSym } from "../new_fields/Doc"; import { NumCast } from "../new_fields/Types"; import { CollectionFreeFormView } from "../client/views/collections/collectionFreeForm/CollectionFreeFormView"; import { Rect } from "react-measure"; +import { Scripting } from "../client/util/Scripting"; export namespace GestureUtils { - namespace GestureDataTypes { - export type BoxData = Array; - } - export class GestureEvent { constructor( readonly gesture: Gestures, readonly points: PointData[], readonly bounds: Rect, - readonly callbackFn?: Function + readonly callbackFn?: Function, + readonly text?: any ) { } } @@ -38,7 +36,8 @@ export namespace GestureUtils { Box = "box", Line = "line", Stroke = "stroke", - Scribble = "scribble" + Scribble = "scribble", + Text = "text" } export const GestureRecognizer = new NDollarRecognizer(false); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 6c37380f3..b0ea2f9ad 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -137,7 +137,7 @@ export class CurrentUserUtils { thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" }); userDoc.thumbDoc = thumbDoc; } - return userDoc.thumbDoc; + return Cast(userDoc.thumbDoc, Doc); } static setupMobileDoc(userDoc: Doc) { -- cgit v1.2.3-70-g09d2 From 8767ec49fbb927ccde96f9f89562109703535d4e Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Thu, 6 Feb 2020 12:40:57 -0500 Subject: asdjklf --- src/client/documents/Documents.ts | 1 + src/client/views/GestureOverlay.scss | 13 +-- src/client/views/GestureOverlay.tsx | 127 ++++++--------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + .../collectionFreeForm/MarqueeOptionsMenu.tsx | 8 ++ .../collections/collectionFreeForm/MarqueeView.tsx | 29 ++++- 6 files changed, 78 insertions(+), 101 deletions(-) (limited to 'src/client/views') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 64583ae55..1c13eb079 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -138,6 +138,7 @@ export interface DocumentOptions { textTransform?: string; // is linear view expanded letterSpacing?: string; // is linear view expanded flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse"; + selectedIndex?: number; } class EmptyBox { diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index f425c438e..2fad87ee0 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -17,19 +17,18 @@ position: absolute; width: 300px; overflow: hidden; + pointer-events: none; .inkToTextDoc-scroller { overflow: visible; position: absolute; width: 100%; - left: -24px; - .collectionView { - overflow: visible; - - .collectionLinearView-outer { - overflow: visible; - } + .menuItem-cont { + width: 100%; + height: 20px; + padding: 2.5px; + border-bottom: .5px solid black; } } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 0fd6f3dba..e8262af1b 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -26,7 +26,7 @@ import { ScriptField } from "../../new_fields/ScriptField"; import { listSpec } from "../../new_fields/Schema"; import { List } from "../../new_fields/List"; import { CollectionViewType } from "./collections/CollectionView"; -import InkCanvas from "./InkCanvas"; +import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; @observer export default class GestureOverlay extends Touchable { @@ -40,12 +40,15 @@ export default class GestureOverlay extends Touchable { @observable private _thumbX?: number; @observable private _thumbY?: number; + @observable private _selectedIndex: number = -1; + @observable private _menuX: number = -300; + @observable private _menuY: number = -300; @observable private _pointerY?: number; @observable private _points: { X: number, Y: number }[] = []; @observable private _strokes: InkData[] = []; @observable private _palette?: JSX.Element; @observable private _clipboardDoc?: JSX.Element; - @observable private _possibilities: Doc[] = []; + @observable private _possibilities: JSX.Element[] = []; @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); } @computed private get showBounds() { return this.Tool !== ToolglassTools.None; } @@ -254,6 +257,8 @@ export default class GestureOverlay extends Touchable { this._thumbDoc = thumbDoc; this._thumbX = thumb.clientX; this._thumbY = thumb.clientY; + this._menuX = thumb.clientX + 50; + this._menuY = thumb.clientY; this._palette = ; }); } @@ -294,15 +299,14 @@ export default class GestureOverlay extends Touchable { const pt = e.changedTouches.item(i); if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) { if (this._thumbX && this._thumbDoc) { - if (Math.abs(pt.clientX - this._thumbX) > 20) { + if (Math.abs(pt.clientX - this._thumbX) > 30) { this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); this._thumbX = pt.clientX; } } if (this._thumbY && this._inkToTextDoc) { if (Math.abs(pt.clientY - this._thumbY) > 20) { - this._inkToTextDoc.selectedIndex = Math.max(0, NumCast(this._inkToTextDoc.selectedIndex) - Math.sign(pt.clientY - this._thumbY)); - this._thumbY = pt.clientY; + this._selectedIndex = Math.max(0, -Math.floor((pt.clientY - this._thumbY) / 20)); } } } @@ -324,11 +328,10 @@ export default class GestureOverlay extends Touchable { let scriptWorked = false; if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) { const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex)]; - if (Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton }, console.log).success) { + if (selectedButton) { + selectedButton.props.onClick(); scriptWorked = true; - console.log("success"); - }; - console.log(Cast(selectedButton?.proto?.onClick, ScriptField)?.script); + } } if (!scriptWorked) { @@ -337,6 +340,7 @@ export default class GestureOverlay extends Touchable { }); } this._strokes = []; + this._points = []; this._possibilities = []; document.removeEventListener("touchend", this.handleHandUp); } @@ -398,12 +402,12 @@ export default class GestureOverlay extends Touchable { } @action - onPointerUp = async (e: PointerEvent) => { + onPointerUp = (e: PointerEvent) => { if (this._points.length > 1) { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - const initialPoint = this._points[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; const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); @@ -414,28 +418,21 @@ export default class GestureOverlay extends Touchable { document.removeEventListener("pointerup", this.onPointerUp); this._strokes.push(new Array(...this._points)); this._points = []; - const results = await CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes); - const wordResults = results.filter((r: any) => r.category === "inkWord"); - const possibilities: string[] = []; - if (wordResults[0]?.recognizedText) { - possibilities.push(wordResults[0]?.recognizedText) - } - possibilities.push(...wordResults[0]?.alternates?.map((a: any) => a.recognizedString)); - const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); - const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); - const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); - runInAction(() => { - const buttons = possibilities.map(p => Docs.Create.ButtonDocument({ - _height: 25, _width: r - l, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t, _viewType: CollectionViewType.Linear, - isExpanded: true, - title: p, fontSize: 10, letterSpacing: "0px", textTransform: "unset", boxShadow: ".5px .5px 0px rgb(34, 34, 34)", - onClick: ScriptField.MakeScript('createText(this.proto.title, this.x, this.y)') - })); - if (this._inkToTextDoc && this._thumbDoc) { - this._inkToTextDoc = this._thumbDoc.inkToTextDoc = Docs.Create.LinearDocument(buttons, { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" }); - this._inkToTextDoc.selectedIndex = 0; + CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => { + const wordResults = results.filter((r: any) => r.category === "inkWord" || r.category === "paragraph"); + const possibilities: string[] = []; + if (wordResults[0]?.recognizedText) { + possibilities.push(wordResults[0]?.recognizedText) } - this._possibilities = buttons; + possibilities.push(...wordResults[0]?.alternates?.map((a: any) => a.recognizedString)); + console.log(possibilities); + const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); + const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); + const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); + runInAction(() => { + this._possibilities = possibilities.map(p => + GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />); + }); }); break; case ToolglassTools.IgnoreGesture: @@ -511,10 +508,9 @@ export default class GestureOverlay extends Touchable { return [ this.props.children, this._palette, - [this._strokes.map(l => { const b = this.getBounds(l); - return + return {InteractionUtils.CreatePolyline(l, b.left, b.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)} ; }), @@ -558,58 +554,6 @@ export default class GestureOverlay extends Touchable { this._clipboardDoc = undefined; } - @computed - private get suggestionContent() { - const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom)); - const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); - const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); - const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); - return ( -
-
- new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)} - ContentScaling={returnOne} - PanelWidth={() => 300} - PanelHeight={() => 300} - renderDepth={0} - backgroundColor={returnEmptyString} - focus={emptyFunction} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} - bringToFront={emptyFunction} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne} - />; -
-
-
-
) - } - - private get inkToTextSuggestions() { - if (this._inkToTextDoc && this._possibilities.length) { - return ( - this.suggestionContent - ); - } - return null; - } - render() { trace(); return ( @@ -618,8 +562,8 @@ export default class GestureOverlay extends Touchable {
@@ -627,14 +571,14 @@ export default class GestureOverlay extends Touchable {
- {this.inkToTextSuggestions} +
); } } @@ -666,6 +610,5 @@ Scripting.addGlobal(function resetPen() { }); }); Scripting.addGlobal(function createText(text: any, x: any, y: any) { - console.log("creating"); GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text); }); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 17139d9d9..2b160f2ff 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -984,6 +984,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ; } + @computed get contentScaling() { if (this.props.annotationsKey) return 0; const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 71f265484..db4b674b5 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -11,6 +11,7 @@ export default class MarqueeOptionsMenu extends AntimodeMenu { public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => 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; @@ -43,6 +44,13 @@ export default class MarqueeOptionsMenu extends AntimodeMenu { onPointerDown={this.delete}> , + , ]; return this.getElement(buttons); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ef2fc2ad1..112df8f05 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,12 +1,12 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../../new_fields/Doc"; +import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc"; import { InkField } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; import { listSpec } from "../../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; import { ComputedField } from "../../../../new_fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types"; import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; import { Utils } from "../../../../Utils"; import { Docs } from "../../../documents/Documents"; @@ -19,6 +19,7 @@ import "./MarqueeView.scss"; import React = require("react"); import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import { SubCollectionViewProps } from "../CollectionSubView"; +import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -204,6 +205,7 @@ export class MarqueeView extends React.Component { + const selected = this.marqueeSelect(false); + if (e instanceof KeyboardEvent ? e.key === "i" : true) { + const inks = selected.filter(s => s.proto?.type === "ink"); + const sets = selected.filter(s => s.proto?.type === "text") + const inkFields = inks.map(i => FieldValue(Cast(i.data, InkField))); + CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => { + const wordResults = results.filter((r: any) => r.category === "inkWord"); + console.log(wordResults); + for (const word of wordResults) { + const indices: number[] = word.strokeIds; + const r = Math.floor(Math.random() * 256); + const g = Math.floor(Math.random() * 256); + const b = Math.floor(Math.random() * 256); + indices.forEach(i => { + inks[i].color = `rgb(${r}, ${g}, ${b})`; + }) + } + }); + } + } + @action summary = (e: KeyboardEvent | React.PointerEvent | undefined) => { const bounds = this.Bounds; -- cgit v1.2.3-70-g09d2 From 483aa393cd004e97e2722fea52e920379099cb4d Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Thu, 6 Feb 2020 16:59:52 -0500 Subject: oops --- src/client/views/TouchScrollableMenu.tsx | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/client/views/TouchScrollableMenu.tsx (limited to 'src/client/views') diff --git a/src/client/views/TouchScrollableMenu.tsx b/src/client/views/TouchScrollableMenu.tsx new file mode 100644 index 000000000..4bda0818e --- /dev/null +++ b/src/client/views/TouchScrollableMenu.tsx @@ -0,0 +1,59 @@ +import React = require("react"); +import { computed } from "mobx"; +import { observer } from "mobx-react"; + +export interface TouchScrollableMenuProps { + options: JSX.Element[]; + bounds: { + right: number; + left: number; + bottom: number; + top: number; + width: number; + height: number; + }; + selectedIndex: number; + x: number; + y: number; +} + +export interface TouchScrollableMenuItemProps { + text: string; + onClick: () => any; +} + +@observer +export default class TouchScrollableMenu extends React.Component { + + @computed + private get possibilities() { return this.props.options; } + + @computed + private get selectedIndex() { return this.props.selectedIndex; } + + render() { + return ( +
+
+ {this.possibilities} +
+
+
+
+ ) + } +} + +export class TouchScrollableMenuItem extends React.Component{ + render() { + return ( +
+ {this.props.text} +
+ ) + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From a943eec770eb32543d4847466b718739911a49a1 Mon Sep 17 00:00:00 2001 From: andrewdkim Date: Sat, 8 Feb 2020 13:02:55 -0500 Subject: radial gesture --- src/client/views/GestureOverlay.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'src/client/views') diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index e8262af1b..3631d3204 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -366,6 +366,22 @@ export default class GestureOverlay extends Touchable { this._points.push({ X: e.clientX, Y: e.clientY }); e.stopPropagation(); e.preventDefault(); + + + if (this._points.length > 1) { + const B = this.svgBounds; + 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; + const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); + if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { + switch (this.Tool) { + case ToolglassTools.RadialMenu: + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + //this.handle1PointerHoldStart(e); + } + } + } } } @@ -588,6 +604,7 @@ export default class GestureOverlay extends Touchable { export enum ToolglassTools { InkToText = "inktotext", IgnoreGesture = "ignoregesture", + RadialMenu = "radialmenu", None = "none", } -- cgit v1.2.3-70-g09d2 From 593016303351f365d6ae13413b72d77495ea6a03 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Sat, 8 Feb 2020 14:35:20 -0500 Subject: syntax highlighting :) --- src/client/documents/Documents.ts | 1 + src/client/views/GestureOverlay.tsx | 22 +++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 74 +++++++++++++++++++++- .../collections/collectionFreeForm/MarqueeView.tsx | 24 +++++-- src/pen-gestures/GestureUtils.ts | 2 + src/pen-gestures/ndollar.ts | 11 +++- 6 files changed, 119 insertions(+), 15 deletions(-) (limited to 'src/client/views') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1c13eb079..81a6ff802 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -139,6 +139,7 @@ export interface DocumentOptions { letterSpacing?: string; // is linear view expanded flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse"; selectedIndex?: number; + syntaxColor?: string; // can be applied to text for syntax highlighting all matches in the text } class EmptyBox { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index e8262af1b..0b77b4b86 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -50,7 +50,7 @@ export default class GestureOverlay extends Touchable { @observable private _clipboardDoc?: JSX.Element; @observable private _possibilities: JSX.Element[] = []; - @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); } + @computed private get height(): number { return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); } @computed private get showBounds() { return this.Tool !== ToolglassTools.None; } private _d1: Doc | undefined; @@ -408,8 +408,8 @@ export default class GestureOverlay extends Touchable { const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); 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; - const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); + const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height); + const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - (this.height) && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { switch (this.Tool) { @@ -450,6 +450,14 @@ export default class GestureOverlay extends Touchable { this.dispatchGesture(GestureUtils.Gestures.Box); actionPerformed = true; break; + case GestureUtils.Gestures.StartBracket: + this.dispatchGesture(GestureUtils.Gestures.StartBracket); + actionPerformed = true; + break; + case GestureUtils.Gestures.EndBracket: + this.dispatchGesture(GestureUtils.Gestures.EndBracket); + actionPerformed = true; + break; case GestureUtils.Gestures.Line: actionPerformed = this.handleLineGesture(); break; @@ -562,8 +570,8 @@ export default class GestureOverlay extends Touchable {
@@ -571,8 +579,8 @@ export default class GestureOverlay extends Touchable {
= new Map(); private _clusterDistance: number = 75; private _hitCluster = false; private _layoutComputeReaction: IReactionDisposer | undefined; @@ -411,11 +416,78 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 })); sel.forEach(d => this.props.removeDocument(d)); + e.stopPropagation(); + break; + case GestureUtils.Gestures.StartBracket: + const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y))); + this._inkToTextStartX = start[0]; + this._inkToTextStartY = start[1]; + console.log("start"); + break; + case GestureUtils.Gestures.EndBracket: + console.log("end"); + if (this._inkToTextStartX && this._inkToTextStartY) { + const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); + const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "text" && s.color); + const sets = setDocs.map((sd) => { + return Cast(sd.data, RichTextField)?.Text as string; + }); + if (sets.length && sets[0]) { + this._wordPalette.clear(); + const colors = setDocs.map(sd => FieldValue(sd.color) as string); + sets.forEach((st: string, i: number) => { + const words = st.split(","); + words.forEach(word => { + this._wordPalette.set(word, colors[i]); + }); + }); + } + const inks = this.getActiveDocuments().filter(doc => { + if (doc.type === "ink") { + const l = NumCast(doc.x); + const r = l + doc[WidthSym](); + const t = NumCast(doc.y); + const b = t + doc[HeightSym](); + const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t); + return pass; + } + return false; + }); + const inkFields = inks.map(i => Cast(i.data, InkField)); + CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => { + const wordResults = results.filter((r: any) => r.category === "inkWord"); + console.log(wordResults); + for (const word of wordResults) { + const indices: number[] = word.strokeIds; + indices.forEach(i => { + const otherInks: Doc[] = []; + indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2])); + inks[i].relatedInks = new List(otherInks); + const uniqueColors: string[] = []; + Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c)); + inks[i].alternativeColors = new List(uniqueColors); + if (this._wordPalette.has(word.recognizedText)) { + inks[i].color = this._wordPalette.get(word.recognizedText); + } + else { + for (const alt of word.alternates) { + if (this._wordPalette.has(alt.recognizedString)) { + inks[i].color = this._wordPalette.get(alt.recognizedString); + break; + } + } + } + }); + } + }); + this._inkToTextStartX = end[0]; + } break; case GestureUtils.Gestures.Text: if (ge.text) { const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y); this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] })); + e.stopPropagation(); } } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 112df8f05..19a71012a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -20,6 +20,7 @@ import React = require("react"); import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import { SubCollectionViewProps } from "../CollectionSubView"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; +import { RichTextField } from "../../../../new_fields/RichTextField"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -368,18 +369,29 @@ export class MarqueeView extends React.Component s.proto?.type === "ink"); - const sets = selected.filter(s => s.proto?.type === "text") - const inkFields = inks.map(i => FieldValue(Cast(i.data, InkField))); + const setDocs = selected.filter(s => s.proto?.type === "text" && s.color); + const sets = setDocs.map((sd) => { + return Cast(sd.data, RichTextField)?.Text as string; + }); + const colors = setDocs.map(sd => FieldValue(sd.color) as string); + const wordToColor = new Map(); + console.log(sets); + sets.forEach((st: string, i: number) => { + const words = st.split(","); + words.forEach(word => { + wordToColor.set(word, colors[i]); + }); + }); + const inkFields = inks.map(i => Cast(i.data, InkField)); CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => { const wordResults = results.filter((r: any) => r.category === "inkWord"); console.log(wordResults); for (const word of wordResults) { const indices: number[] = word.strokeIds; - const r = Math.floor(Math.random() * 256); - const g = Math.floor(Math.random() * 256); - const b = Math.floor(Math.random() * 256); indices.forEach(i => { - inks[i].color = `rgb(${r}, ${g}, ${b})`; + if (wordToColor.has(word.recognizedText)) { + inks[i].color = wordToColor.get(word.recognizedText); + } }) } }); diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 4e3493c1c..f14c573c3 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -35,6 +35,8 @@ export namespace GestureUtils { export enum Gestures { Box = "box", Line = "line", + StartBracket = "startbracket", + EndBracket = "endbracket", Stroke = "stroke", Scribble = "scribble", Text = "text" diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts index 9e15ada2d..643d58591 100644 --- a/src/pen-gestures/ndollar.ts +++ b/src/pen-gestures/ndollar.ts @@ -142,7 +142,7 @@ export class Result { // // NDollarRecognizer constants // -const NumMultistrokes = 2; +const NumMultistrokes = 4; const NumPoints = 96; const SquareSize = 250.0; const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35) @@ -178,6 +178,15 @@ export class NDollarRecognizer { this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array( new Array(new Point(12, 347), new Point(119, 347)) )); + this.Multistrokes[2] = new Multistroke(GestureUtils.Gestures.StartBracket, useBoundedRotationInvariance, new Array( + // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150)) + new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150)) + )); + this.Multistrokes[3] = new Multistroke(GestureUtils.Gestures.EndBracket, useBoundedRotationInvariance, new Array( + // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152)) + // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150)) + new Array(new Point(10, 100), new Point(50, 12), new Point(100, 103)) + )); // // PREDEFINED STROKES -- cgit v1.2.3-70-g09d2 From 87167fd126e161b29d8d798a5f04e3cf159aae16 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Sat, 8 Feb 2020 15:58:07 -0500 Subject: some fixes to thumb movements --- package-lock.json | 27 +++++--- src/client/views/GestureOverlay.scss | 2 +- src/client/views/GestureOverlay.tsx | 107 +++++++++++++++++++------------- src/client/views/MainView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 +- 5 files changed, 86 insertions(+), 56 deletions(-) (limited to 'src/client/views') diff --git a/package-lock.json b/package-lock.json index 31524932c..825890038 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5391,7 +5391,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5428,7 +5429,8 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", @@ -5437,7 +5439,8 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5551,6 +5554,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5576,6 +5580,7 @@ "minipass": { "version": "2.9.0", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5592,6 +5597,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5683,6 +5689,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5758,7 +5765,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5788,6 +5796,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5805,6 +5814,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5843,7 +5853,8 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.1.1", @@ -13826,7 +13837,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -15742,7 +15753,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -18314,4 +18325,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index 2fad87ee0..7474ca839 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -26,7 +26,7 @@ .menuItem-cont { width: 100%; - height: 20px; + height: 25px; padding: 2.5px; border-bottom: .5px solid black; } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index cd535bd53..b23d4d020 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -130,7 +130,7 @@ export default class GestureOverlay extends Touchable { if (nts.nt.length < 5) { const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); - target ?.dispatchEvent( + target?.dispatchEvent( new CustomEvent>("dashOnTouchStart", { bubbles: true, @@ -144,34 +144,34 @@ export default class GestureOverlay extends Touchable { } ) ); - if (this.prevPoints.size === 1 && this._holdTimer === undefined) { - console.log("started"); - this._holdTimer = setTimeout(() => { - console.log("hold"); - const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); - target ?.dispatchEvent( - new CustomEvent>("dashOnTouchHoldStart", - { - bubbles: true, - detail: { - fingers: this.prevPoints.size, - targetTouches: nts.ntt, - touches: nts.nt, - changedTouches: nts.nct, - touchEvent: te - } - } - ) - ); - this._holdTimer = undefined; - document.removeEventListener("touchmove", this.onReactTouchMove); - document.removeEventListener("touchend", this.onReactTouchEnd); - document.removeEventListener("touchmove", this.onReactHoldTouchMove); - document.removeEventListener("touchend", this.onReactHoldTouchEnd); - document.addEventListener("touchmove", this.onReactHoldTouchMove); - document.addEventListener("touchend", this.onReactHoldTouchEnd); - }, (1000)); - } + // if (this.prevPoints.size === 1 && this._holdTimer === undefined) { + // console.log("started"); + // this._holdTimer = setTimeout(() => { + // console.log("hold"); + // const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); + // target?.dispatchEvent( + // new CustomEvent>("dashOnTouchHoldStart", + // { + // bubbles: true, + // detail: { + // fingers: this.prevPoints.size, + // targetTouches: nts.ntt, + // touches: nts.nt, + // changedTouches: nts.nct, + // touchEvent: te + // } + // } + // ) + // ); + // this._holdTimer = undefined; + // document.removeEventListener("touchmove", this.onReactTouchMove); + // document.removeEventListener("touchend", this.onReactTouchEnd); + // document.removeEventListener("touchmove", this.onReactHoldTouchMove); + // document.removeEventListener("touchend", this.onReactHoldTouchEnd); + // document.addEventListener("touchmove", this.onReactHoldTouchMove); + // document.addEventListener("touchend", this.onReactHoldTouchEnd); + // }, (1000)); + // } document.removeEventListener("touchmove", this.onReactTouchMove); document.removeEventListener("touchend", this.onReactTouchEnd); document.addEventListener("touchmove", this.onReactTouchMove); @@ -304,7 +304,7 @@ export default class GestureOverlay extends Touchable { if (pt.radiusX > 1 && pt.radiusY > 1) { for (let j = 0; j < e.targetTouches.length; j++) { const tPt = e.targetTouches.item(j); - if (tPt ?.screenX === pt ?.screenX && tPt ?.screenY === pt ?.screenY) { + if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) { if (pt && this.prevPoints.has(pt.identifier)) { fingers.push(pt); } @@ -392,17 +392,32 @@ export default class GestureOverlay extends Touchable { for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) { - if (this._thumbX && this._thumbDoc) { - if (Math.abs(pt.clientX - this._thumbX) > 30) { - this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); - this._thumbX = pt.clientX; + if (this._thumbX && this._thumbY) { + const yOverX = Math.abs(pt.clientX - this._thumbX) < Math.abs(pt.clientY - this._thumbY); + if ((yOverX && this._inkToTextDoc) || this._selectedIndex > 0) { + if (Math.abs(pt.clientY - this._thumbY) > 20) { + this._selectedIndex = Math.min(Math.max(0, -Math.ceil((pt.clientY - this._thumbY) / 20)), this._possibilities.length - 1); + } } - } - if (this._thumbY && this._inkToTextDoc) { - if (Math.abs(pt.clientY - this._thumbY) > 20) { - this._selectedIndex = Math.max(0, -Math.floor((pt.clientY - this._thumbY) / 20)); + else if (this._thumbDoc) { + if (Math.abs(pt.clientX - this._thumbX) > 30) { + this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); + this._thumbX = pt.clientX; + } } } + + // if (this._thumbX && this._thumbDoc) { + // if (Math.abs(pt.clientX - this._thumbX) > 30) { + // this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); + // this._thumbX = pt.clientX; + // } + // } + // if (this._thumbY && this._inkToTextDoc) { + // if (Math.abs(pt.clientY - this._thumbY) > 20) { + // this._selectedIndex = Math.min(Math.max(0, -Math.ceil((pt.clientY - this._thumbY) / 20)), this._possibilities.length - 1); + // } + // } } if (pt && pt.identifier === this.pointerIdentifier) { this._pointerY = pt.clientY; @@ -506,8 +521,8 @@ export default class GestureOverlay extends Touchable { callbackFn: callback } }); - target1 ?.dispatchEvent(ge); - target2 ?.dispatchEvent(ge); + target1?.dispatchEvent(ge); + target2?.dispatchEvent(ge); return actionPerformed; } @@ -529,12 +544,16 @@ export default class GestureOverlay extends Touchable { this._strokes.push(new Array(...this._points)); this._points = []; CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => { - const wordResults = results.filter((r: any) => r.category === "inkWord" || r.category === "paragraph"); + console.log(results); + const wordResults = results.filter((r: any) => r.category === "line"); const possibilities: string[] = []; - if (wordResults[0]?.recognizedText) { - possibilities.push(wordResults[0]?.recognizedText) + for (const wR of wordResults) { + console.log(wR); + if (wR?.recognizedText) { + possibilities.push(wR?.recognizedText) + } + possibilities.push(...wR?.alternates?.map((a: any) => a.recognizedString)); } - possibilities.push(...wordResults[0]?.alternates?.map((a: any) => a.recognizedString)); console.log(possibilities); const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 87a81504c..4e81f528a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -526,7 +526,7 @@ export class MainView extends React.Component { - + {/* */} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 925a590ca..4a5613428 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -170,7 +170,7 @@ export class DocumentView extends DocComponent(Docu this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); - this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); + // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); } @@ -184,7 +184,7 @@ export class DocumentView extends DocComponent(Docu this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); - this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); + // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); } @action -- cgit v1.2.3-70-g09d2 From 28efd6f2b5b79d1f25fa66e5d9f69d77a7594fee Mon Sep 17 00:00:00 2001 From: vellichora Date: Sat, 8 Feb 2020 20:48:51 -0500 Subject: refactored mobile interface so that current views are created via scripting --- src/client/views/nodes/WebBox.tsx | 6 +- src/mobile/ImageUpload.tsx | 10 +- src/mobile/MobileInkOverlay.tsx | 6 +- src/mobile/MobileInterface.tsx | 115 +++++++++++++-------- .../authentication/models/current_user_utils.ts | 13 ++- 5 files changed, 92 insertions(+), 58 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index b35ea0bb0..0b23c3bec 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -182,9 +182,11 @@ export class WebBox extends DocAnnotatableComponent {view}
; - const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + const decInteracting = DocumentDecorations.Instance && DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const frozen = !this.props.isSelected() || decInteracting; + + const classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !decInteracting ? "-interactive" : ""); return ( <>
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index 0b0280519..3304e8e22 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -13,6 +13,7 @@ import { observable } from 'mobx'; import { Utils } from '../Utils'; import MobileInterface from './MobileInterface'; import { CurrentUserUtils } from '../server/authentication/models/current_user_utils'; +import { Scripting } from '../client/util/Scripting'; @@ -27,12 +28,11 @@ const inputRef = React.createRef(); @observer class Uploader extends React.Component { - @observable - error: string = ""; - @observable - status: string = ""; + @observable error: string = ""; + @observable status: string = ""; onClick = async () => { + console.log("uploader click"); try { this.status = "initializing protos"; await Docs.Prototypes.initialize(); @@ -105,6 +105,8 @@ class Uploader extends React.Component { } +// Scripting.addGlobal(function uploadImageMobile() { return Uploader.onClick(); }); + // DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload"); (async () => { diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx index 600e8a91b..6b65aa436 100644 --- a/src/mobile/MobileInkOverlay.tsx +++ b/src/mobile/MobileInkOverlay.tsx @@ -37,9 +37,9 @@ export default class MobileInkOverlay extends React.Component { initMobileInkOverlay(content: MobileInkOverlayContent) { const { width, height } = content; const scaledSize = this.initialSize(width ? width : 0, height ? height : 0); - this._width = scaledSize.width * .8; - this._height = scaledSize.height * .8; - this._scale = .8; //scaledSize.scale; + this._width = scaledSize.width; + this._height = scaledSize.height; + this._scale = scaledSize.scale; //scaledSize.scale; this._x = 300; // TODO: center on screen this._y = 25; // TODO: center on screen } diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 03bcbca80..665d9a168 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -30,14 +30,14 @@ export default class MobileInterface extends React.Component { @observable static Instance: MobileInterface; @computed private get userDoc() { return CurrentUserUtils.UserDocument; } @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; } - @observable private currentView: "main" | "ink" | "upload" = "main"; + // @observable private currentView: "main" | "ink" | "upload" = "main"; + private mainDoc: Doc = CurrentUserUtils.setupMobileDoc(this.userDoc); + @observable private renderView?: () => JSX.Element; - private mainDoc = CurrentUserUtils.setupMobileDoc(this.userDoc); - - private inkDoc?: Doc; + // private inkDoc?: Doc; public drawingInk: boolean = false; - private uploadDoc?: Doc; + // private uploadDoc?: Doc; constructor(props: Readonly<{}>) { super(props); @@ -55,41 +55,63 @@ export default class MobileInterface extends React.Component { } @action - switchCurrentView = (view: "main" | "ink" | "upload") => { - this.currentView = view; + switchCurrentView = (doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => { + if (!this.userDoc) return; - if (this.userDoc) { - switch (view) { - case "main": { - // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); - this.userDoc.activeMobile = this.mainDoc; - break; - } - case "ink": { - this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc); - this.userDoc.activeMobile = this.inkDoc; - InkingControl.Instance.switchTool(InkTool.Pen); - this.drawingInk = true; + this.userDoc.activeMobile = doc(this.userDoc); + onSwitch && onSwitch(); - DocServer.Mobile.dispatchOverlayTrigger({ - enableOverlay: true, - width: window.innerWidth, - height: window.innerHeight - }); + this.renderView = renderView; + console.log("switching current view", renderView); + } - break; - } - case "upload": { - this.uploadDoc = CurrentUserUtils.setupMobileUploadDoc(this.userDoc); - this.userDoc.activeMobile = this.uploadDoc; + onSwitchInking = () => { + InkingControl.Instance.switchTool(InkTool.Pen); + MobileInterface.Instance.drawingInk = true; - } - } - } + DocServer.Mobile.dispatchOverlayTrigger({ + enableOverlay: true, + width: window.innerWidth, + height: window.innerHeight + }); } - @computed - get mainContent() { + // @action + // switchCurrentView = (view: "main" | "ink" | "upload") => { + // this.currentView = view; + + // if (this.userDoc) { + // switch (view) { + // case "main": { + // // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); + // this.userDoc.activeMobile = this.mainDoc; + // break; + // } + // case "ink": { + // this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc); + // this.userDoc.activeMobile = this.inkDoc; + // InkingControl.Instance.switchTool(InkTool.Pen); + // this.drawingInk = true; + + // DocServer.Mobile.dispatchOverlayTrigger({ + // enableOverlay: true, + // width: window.innerWidth, + // height: window.innerHeight + // }); + + // break; + // } + // case "upload": { + // this.uploadDoc = CurrentUserUtils.setupMobileUploadDoc(this.userDoc); + // this.userDoc.activeMobile = this.uploadDoc; + + // } + // } + // } + // } + + renderDefaultContent = () => { + console.log("rendering default content"); if (this.mainContainer) { return { - this.switchCurrentView("main"); + this.switchCurrentView((userDoc: Doc) => this.mainDoc); InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool DocServer.Mobile.dispatchOverlayTrigger({ @@ -130,7 +152,7 @@ export default class MobileInterface extends React.Component { height: window.innerHeight }); - this.inkDoc = undefined; + // this.inkDoc = undefined; this.drawingInk = false; } @@ -151,8 +173,8 @@ export default class MobileInterface extends React.Component { e.stopPropagation(); } - @computed - get inkContent() { + renderInkingContent = () => { + console.log("rendering inking content"); // TODO: support panning and zooming // TODO: handle moving of ink strokes if (this.mainContainer) { @@ -202,8 +224,7 @@ export default class MobileInterface extends React.Component { } - @computed - get uploadContent() { + renderUploadContent() { if (this.mainContainer) { return (
@@ -246,15 +267,19 @@ export default class MobileInterface extends React.Component { } render() { - const content = this.currentView === "main" ? this.mainContent : - this.currentView === "ink" ? this.inkContent : - this.currentView === "upload" ? this.uploadContent : <>; + // const content = this.currentView === "main" ? this.mainContent : + // this.currentView === "ink" ? this.inkContent : + // this.currentView === "upload" ? this.uploadContent : <>; return (
- {content} + {this.renderView ? this.renderView() : this.renderDefaultContent()}
); } } -Scripting.addGlobal(function switchMobileView(view: "main" | "ink" | "upload") { return MobileInterface.Instance.switchCurrentView(view); }); +Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); }); +Scripting.addGlobal(function onSwitchMobileInking() { return MobileInterface.Instance.onSwitchInking(); }); +Scripting.addGlobal(function renderMobileInking() { return MobileInterface.Instance.renderInkingContent(); }); +Scripting.addGlobal(function renderMobileUpload() { return MobileInterface.Instance.renderUploadContent(); }); + diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 82bed76f7..817cf40b1 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -14,6 +14,7 @@ import { Utils } from "../../../Utils"; import { nullAudio } from "../../../new_fields/URLField"; import { DragManager } from "../../../client/util/DragManager"; import { InkingControl } from "../../../client/views/InkingControl"; +import { Scripting } from "../../../client/util/Scripting"; export class CurrentUserUtils { private static curr_id: string; @@ -101,8 +102,9 @@ export class CurrentUserUtils { { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc }, { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc }, { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc }, - { title: "draw", icon: "pen-nib", click: 'switchMobileView("ink");', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "red", activePen: doc }, - { title: "upload", icon: "upload", click: 'switchMobileView("upload");', backgroundColor: "orange" }, + { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "red", activePen: doc }, + { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload);', backgroundColor: "orange" }, + { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" }, ]; return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, @@ -147,7 +149,7 @@ export class CurrentUserUtils { static setupMobileUploadDoc(userDoc: Doc) { console.log("setup mobile upload", window.innerWidth, window.innerHeight); - const webDoc = Docs.Create.WebDocument("https://wikipedia.com", { title: "Mobile Upload Web", chromeStatus: "enabled", ignoreClick: true }); + const webDoc = Docs.Create.WebDocument("https://wikipedia.com", { title: "Mobile Upload Web", chromeStatus: "enabled" }); const uploadDoc = Docs.Create.StackingDocument([], { title: "Mobile Upload", backgroundColor: "pink" }); return Docs.Create.StackingDocument([webDoc, uploadDoc], { title: "Mobile Upload", backgroundColor: "white", @@ -388,4 +390,7 @@ export class CurrentUserUtils { }; return recurs([] as Attribute[], schema ? schema.rootAttributeGroup : undefined); } -} \ No newline at end of file +} + +Scripting.addGlobal(function setupMobileInkingDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileInkingDoc(userDoc); }); +Scripting.addGlobal(function setupMobileUploadDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileUploadDoc(userDoc); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 1e36055e3674fe33dd39f6ad79c969531f7b447c Mon Sep 17 00:00:00 2001 From: eeng5 Date: Sun, 9 Feb 2020 14:59:54 -0500 Subject: drop metadata --- package-lock.json | 3 ++- package.json | 6 +++--- src/client/util/DragManager.ts | 2 +- src/client/views/GestureOverlay.tsx | 8 ++++---- src/client/views/Main.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 25 ++++++++++++++++++++++--- 6 files changed, 33 insertions(+), 12 deletions(-) (limited to 'src/client/views') diff --git a/package-lock.json b/package-lock.json index 825890038..cee97e4d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4240,7 +4240,8 @@ "dotenv": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "dev": true }, "duplexer": { "version": "0.1.1", diff --git a/package.json b/package.json index ed3458378..994ac816b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start-release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", - "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", + "start": "cross-env HANDWRITING='61088486d76c4b12ba578775a5f55422' NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --inspect -- src/server/index.ts", "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production", "test": "mocha -r ts-node/register test/**/*.ts", @@ -22,6 +22,7 @@ "copy-webpack-plugin": "^4.6.0", "cross-env": "^5.2.1", "css-loader": "^2.1.1", + "dotenv": "^8.2.0", "file-loader": "^3.0.1", "fork-ts-checker-webpack-plugin": "^1.6.0", "jsdom": "^15.2.1", @@ -137,7 +138,6 @@ "cors": "^2.8.5", "crypto-browserify": "^3.11.0", "d3-format": "^1.4.3", - "dotenv": "^8.2.0", "exif": "^0.6.0", "express": "^4.16.4", "express-flash": "0.0.2", @@ -238,4 +238,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} +} \ No newline at end of file diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c05a2de96..5d2f1bc06 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -402,7 +402,7 @@ export namespace DragManager { if (target) { const complete = new DragCompleteEvent(false, dragData); finishDrag?.(complete); - + console.log(complete.aborted); target.dispatchEvent( new CustomEvent("dashOnDrop", { bubbles: true, diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index b23d4d020..4e9b87eea 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -395,13 +395,13 @@ export default class GestureOverlay extends Touchable { if (this._thumbX && this._thumbY) { const yOverX = Math.abs(pt.clientX - this._thumbX) < Math.abs(pt.clientY - this._thumbY); if ((yOverX && this._inkToTextDoc) || this._selectedIndex > 0) { - if (Math.abs(pt.clientY - this._thumbY) > 20) { - this._selectedIndex = Math.min(Math.max(0, -Math.ceil((pt.clientY - this._thumbY) / 20)), this._possibilities.length - 1); + if (Math.abs(pt.clientY - this._thumbY) > (10 * window.devicePixelRatio)) { + this._selectedIndex = Math.min(Math.max(-1, -Math.ceil((pt.clientY - this._thumbY) / 20)), this._possibilities.length - 1); } } else if (this._thumbDoc) { - if (Math.abs(pt.clientX - this._thumbX) > 30) { - this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); + if (Math.abs(pt.clientX - this._thumbX) > (15 * window.devicePixelRatio)) { + this._thumbDoc.selectedIndex = Math.max(-1, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); this._thumbX = pt.clientX; } } diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index b21eb9c8f..6d705aa44 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -5,6 +5,7 @@ import * as ReactDOM from 'react-dom'; import * as React from 'react'; import { DocServer } from "../DocServer"; import { AssignAllExtensions } from "../../extensions/General/Extensions"; +process.env.HANDWRITING = "61088486d76c4b12ba578775a5f55422"; AssignAllExtensions(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4a5613428..4a9a6c867 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -595,9 +595,28 @@ export class DocumentView extends DocComponent(Docu DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`); } - if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) { - Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom"); - e.stopPropagation(); + if (de.complete.docDragData) { + if (de.complete.docDragData.applyAsTemplate) { + Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom"); + e.stopPropagation(); + } + else if (de.complete.docDragData.draggedDocuments[0].type === "text") { + const text = Cast(de.complete.docDragData.draggedDocuments[0].data, RichTextField)?.Text; + if (text && text[0] === "{" && text[text.length - 1] === "}" && text.includes(":")) { + let loc = text.indexOf(":"); + let key = text.slice(1, loc); + let value = text.slice(loc + 1, text.length - 1); + console.log(key); + console.log(value); + console.log(this.props.Document); + this.props.Document[key] = value; + console.log(de.complete.docDragData.draggedDocuments[0].x); + console.log(de.complete.docDragData.draggedDocuments[0].x); + e.preventDefault(); + e.stopPropagation(); + de.complete.aborted = true; + } + } } if (de.complete.linkDragData) { e.stopPropagation(); -- cgit v1.2.3-70-g09d2 From 1e6d81d5f3019a6407ed764e3725611e0228489b Mon Sep 17 00:00:00 2001 From: eeng5 Date: Sun, 9 Feb 2020 15:52:40 -0500 Subject: :asdkfjl --- package.json | 2 +- src/client/views/GestureOverlay.tsx | 56 ++++++++++++++++----------------- src/client/views/MainView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- 4 files changed, 31 insertions(+), 31 deletions(-) (limited to 'src/client/views') diff --git a/package.json b/package.json index 67b9b1630..7e0adfba6 100644 --- a/package.json +++ b/package.json @@ -253,4 +253,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 4e9b87eea..b85f35fe4 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -144,34 +144,34 @@ export default class GestureOverlay extends Touchable { } ) ); - // if (this.prevPoints.size === 1 && this._holdTimer === undefined) { - // console.log("started"); - // this._holdTimer = setTimeout(() => { - // console.log("hold"); - // const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); - // target?.dispatchEvent( - // new CustomEvent>("dashOnTouchHoldStart", - // { - // bubbles: true, - // detail: { - // fingers: this.prevPoints.size, - // targetTouches: nts.ntt, - // touches: nts.nt, - // changedTouches: nts.nct, - // touchEvent: te - // } - // } - // ) - // ); - // this._holdTimer = undefined; - // document.removeEventListener("touchmove", this.onReactTouchMove); - // document.removeEventListener("touchend", this.onReactTouchEnd); - // document.removeEventListener("touchmove", this.onReactHoldTouchMove); - // document.removeEventListener("touchend", this.onReactHoldTouchEnd); - // document.addEventListener("touchmove", this.onReactHoldTouchMove); - // document.addEventListener("touchend", this.onReactHoldTouchEnd); - // }, (1000)); - // } + if (this.prevPoints.size === 1 && this._holdTimer === undefined) { + console.log("started"); + this._holdTimer = setTimeout(() => { + console.log("hold"); + const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); + target?.dispatchEvent( + new CustomEvent>("dashOnTouchHoldStart", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: te + } + } + ) + ); + this._holdTimer = undefined; + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + document.removeEventListener("touchmove", this.onReactHoldTouchMove); + document.removeEventListener("touchend", this.onReactHoldTouchEnd); + document.addEventListener("touchmove", this.onReactHoldTouchMove); + document.addEventListener("touchend", this.onReactHoldTouchEnd); + }, (1000)); + } document.removeEventListener("touchmove", this.onReactTouchMove); document.removeEventListener("touchend", this.onReactTouchEnd); document.addEventListener("touchmove", this.onReactTouchMove); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 92b26b9bc..997266266 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -531,7 +531,7 @@ export class MainView extends React.Component { - {/* */} + diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 765077c4d..6b400113b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -189,7 +189,7 @@ export class DocumentView extends DocComponent(Docu this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); - // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); + this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); } @action -- cgit v1.2.3-70-g09d2 From 05c429412d36531bed4e8ece889479fcf93faea6 Mon Sep 17 00:00:00 2001 From: kimdahey Date: Sun, 9 Feb 2020 16:43:01 -0500 Subject: functioning --- package-lock.json | 4 +- package.json | 2 +- .../views/collections/CollectionDockingView.tsx | 69 ++++++++++++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 40 ++++++++++++- 4 files changed, 110 insertions(+), 5 deletions(-) (limited to 'src/client/views') diff --git a/package-lock.json b/package-lock.json index 833710fe7..049c91fcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16239,7 +16239,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -18560,7 +18560,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "^1.0.1", diff --git a/package.json b/package.json index 67b9b1630..7e0adfba6 100644 --- a/package.json +++ b/package.json @@ -253,4 +253,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 82cb3bc88..7a6d54ac2 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -249,6 +249,75 @@ export class CollectionDockingView extends React.Component(); + private _pullCoords: number[] = [0, 0]; public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @@ -596,7 +598,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); const pt1 = myTouches[0]; const pt2 = myTouches[1]; - console.log(myTouches); if (this.prevPoints.size === 2) { const oldPoint1 = this.prevPoints.get(pt1.identifier); @@ -625,7 +626,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // use the centerx and centery as the "new mouse position" const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - this.pan({ clientX: centerX, clientY: centerY }); + + if (!this._pullCoords[0] && !this._pullCoords[1]) { // if we are not bezel movement + this.pan({ clientX: centerX, clientY: centerY }); + } + this._lastX = centerX; this._lastY = centerY; } @@ -650,6 +655,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; this._lastX = centerX; this._lastY = centerY; + + // determine if we are using a bezel movement + if ((this.props.PanelWidth() - this._lastX) < 100 || this._lastX < 100 || (this.props.PanelHeight() - this._lastY < 100) || this._lastY < 120) { // to account for header + this._pullCoords = [this._lastX, this._lastY]; + } + this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); @@ -660,12 +671,37 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } cleanUpInteractions = () => { + + if (this._pullCoords[0] !== 0 && this._pullCoords[1] !== 0) { // if bezel mvmt was activated + const xDiff = this._pullCoords[0] - this._lastX; + const yDiff = this._pullCoords[1] - this._lastY; + + console.log('went thru', this._pullCoords); + if ((this._lastX < this._pullCoords[0]) && (yDiff < xDiff)) { // pull from right + console.log('pulled from right'); + // CollectionDockingView.AddRightSplit(this.Document, undefined); + CollectionDockingView.AddSplit(this.Document, "right", undefined); + } else if ((this._lastY > this._pullCoords[1]) && (yDiff < xDiff)) { // pull from top + console.log('pulled from top'); + CollectionDockingView.AddSplit(this.Document, "top", undefined); + } else if ((this._lastY < this._pullCoords[1]) && (yDiff > xDiff)) { // pull from bottom + console.log('pulled from bottom'); + CollectionDockingView.AddSplit(this.Document, "bottom", undefined); + } else if ((this._lastX > this._pullCoords[0]) && (yDiff > xDiff)) { // pull from left + console.log('pulled from left'); + CollectionDockingView.AddSplit(this.Document, "left", undefined); + } + } + + this._pullCoords = [0, 0]; + document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); this.removeMoveListeners(); this.removeEndListeners(); } + @action zoom = (pointX: number, pointY: number, deltaY: number): void => { let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1; -- cgit v1.2.3-70-g09d2 From e8fcbbf57b2a2f443d9c280ce10558cf9d51c632 Mon Sep 17 00:00:00 2001 From: vellichora Date: Sun, 9 Feb 2020 17:11:24 -0500 Subject: can upload collection from mobile to desktop --- src/client/DocServer.ts | 9 +- src/client/util/DragManager.ts | 2 +- .../collections/CollectionMasonryViewFieldRow.tsx | 1 + .../views/collections/CollectionStackingView.tsx | 2 + .../CollectionStackingViewFieldColumn.tsx | 1 + src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 +- .../views/collections/CollectionViewChromes.tsx | 1 + src/mobile/ImageUpload.tsx | 1 + src/mobile/MobileInkOverlay.tsx | 53 +++++++- src/mobile/MobileInterface.scss | 8 ++ src/mobile/MobileInterface.tsx | 150 ++++++++++++--------- src/server/ApiManagers/UploadManager.ts | 1 + src/server/Message.ts | 6 + src/server/Websocket/Websocket.ts | 7 +- .../authentication/models/current_user_utils.ts | 21 +-- src/server/server_Initialization.ts | 24 ++-- 17 files changed, 195 insertions(+), 96 deletions(-) (limited to 'src/client/views') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index c03764471..e09251855 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,5 +1,5 @@ import * as OpenSocket from 'socket.io-client'; -import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from "./../server/Message"; +import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message"; import { Opt, Doc } from '../new_fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; @@ -81,6 +81,10 @@ export namespace DocServer { Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content); } + export function dispatchMobileDocumentUpload(content: MobileDocumentUploadContent) { + Utils.Emit(_socket, MessageStore.MobileDocumentUpload, content); + } + } export function init(protocol: string, hostname: string, port: number, identifier: string) { @@ -116,6 +120,9 @@ export namespace DocServer { _socket.addEventListener("receiveUpdateOverlayPosition", (content: UpdateMobileInkOverlayPositionContent) => { MobileInkOverlay.Instance.updatePosition(content); }); + _socket.addEventListener("receiveMobileDocumentUpload", (content: MobileDocumentUploadContent) => { + MobileInkOverlay.Instance.uploadDocument(content); + }); } function errorFunc(): never { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index df2f5fe3c..0bb8b531d 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -179,7 +179,7 @@ export namespace DragManager { ); } element.dataset.canDrop = "true"; - const handler = (e: Event) => dropFunc(e, (e as CustomEvent).detail); + const handler = (e: Event) => { console.log("drop target reveied docs"); dropFunc(e, (e as CustomEvent).detail); }; element.addEventListener("dashOnDrop", handler); return () => { element.removeEventListener("dashOnDrop", handler); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 80752303c..26e0cc35a 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -73,6 +73,7 @@ export class CollectionMasonryViewFieldRow extends React.Component { + console.log("masronry row drop"); this._createAliasSelected = false; if (de.complete.docDragData) { (this.props.parent.Document.dropConverter instanceof ScriptField) && diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 992820fc7..83c90810e 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -240,6 +240,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { + console.log("DROP STACKIN G2"); const where = [de.x, de.y]; let targInd = -1; let plusOne = 0; @@ -270,6 +271,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @undoBatch @action onDrop = async (e: React.DragEvent): Promise => { + console.log("DROP STACKING"); const where = [e.clientX, e.clientY]; let targInd = -1; this._docXfs.map((cd, i) => { diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 39b4e4e1d..65c4b3195 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -57,6 +57,7 @@ export class CollectionStackingViewFieldColumn extends React.Component { + console.log("column drop stacking"); this._createAliasSelected = false; if (de.complete.docDragData) { const key = StrCast(this.props.parent.props.Document.sectionFilter); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index e94f24f2c..b35af2314 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -6,7 +6,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast } from "../../../new_fields/Types"; +import { Cast, StrCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 88023783b..1d399e26f 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -138,7 +138,7 @@ export class CollectionView extends Touchable { let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); - ContextMenu.Instance.clearItems(); + ContextMenu.Instance && ContextMenu.Instance.clearItems(); if (index !== -1) { value.splice(index, 1); return true; diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index a870b6043..01dc21f95 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -231,6 +231,7 @@ export class CollectionViewBaseChrome extends React.Component { + console.log("toggle collapse"); this.props.CollectionView.props.Document.chromeStatus = this.props.CollectionView.props.Document.chromeStatus === "enabled" ? "collapsed" : "enabled"; if (this.props.collapse) { this.props.collapse(this.props.CollectionView.props.Document.chromeStatus !== "enabled"); diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index 3304e8e22..10bd78075 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -47,6 +47,7 @@ class Uploader extends React.Component { const upload = window.location.origin + "/upload"; this.status = "uploading image"; + console.log("uploading image", formData); const res = await fetch(upload, { method: 'POST', body: formData diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx index 6b65aa436..ed4cca5b9 100644 --- a/src/mobile/MobileInkOverlay.tsx +++ b/src/mobile/MobileInkOverlay.tsx @@ -1,9 +1,13 @@ import React = require('react'); import { observer } from "mobx-react"; -import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent } from "../server/Message"; +import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../server/Message"; import { observable, action } from "mobx"; import { GestureUtils } from "../pen-gestures/GestureUtils"; import "./MobileInkOverlay.scss"; +import { StrCast } from '../new_fields/Types'; +import { DragManager } from "../client/util/DragManager"; +import { DocServer } from '../client/DocServer'; +import { Doc } from '../new_fields/Doc'; @observer @@ -67,7 +71,7 @@ export default class MobileInkOverlay extends React.Component { height: bounds.height * this._scale, }; - const target = document.elementFromPoint(points[0].X, points[0].Y); + const target = document.elementFromPoint(this._x + 10, this._y + 10); target?.dispatchEvent( new CustomEvent("dashOnGesture", { @@ -82,6 +86,43 @@ export default class MobileInkOverlay extends React.Component { ); } + uploadDocument = async (content: MobileDocumentUploadContent) => { + const { docId } = content; + console.log("receive upload document id", docId); + const doc = await DocServer.GetRefField(docId); + + if (doc && doc instanceof Doc) { + console.log("parsed upload document into doc", StrCast(doc.proto!.title)); + + const target = document.elementFromPoint(this._x + 10, this._y + 10); + console.log("the target is", target); + + const dragData = new DragManager.DocumentDragData([doc]); + const complete = new DragManager.DragCompleteEvent(false, dragData); + console.log("the drag data is", dragData); + + if (target) { + target.dispatchEvent( + new CustomEvent("dashOnDrop", + { + bubbles: true, + detail: { + x: this._x, + y: this._y, + complete: complete, + altKey: false, + metaKey: false, + ctrlKey: false + } + } + ) + ); + } else { + alert("TARGET IS UNDEFINED"); + } + } + } + @action dragStart = (e: React.PointerEvent) => { document.removeEventListener("pointermove", this.dragging); @@ -132,15 +173,17 @@ export default class MobileInkOverlay extends React.Component { transform: `translate(${this._x}px, ${this._y}px)`, zIndex: 30000, pointerEvents: "none", - borderStyle: this._isDragging ? "solid" : "dashed" - }} + borderStyle: this._isDragging ? "solid" : "dashed", + backgroundColor: "rgba(255, 0, 0, 0.3)" + } + } ref={this._mainCont} >
-
+
); } } \ No newline at end of file diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss index 8abe5a40d..8b0ebcd53 100644 --- a/src/mobile/MobileInterface.scss +++ b/src/mobile/MobileInterface.scss @@ -1,7 +1,15 @@ .mobileInterface-inkInterfaceButtons { position: absolute; + top: -50px; display: flex; justify-content: space-between; width: 100%; z-index: 9999; + height: 50px; +} + +.mobileInterface-container { + height: calc(100% - 50px); + margin-top: 50px; + position: relative; } \ No newline at end of file diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 665d9a168..a1ef0a5d1 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -22,6 +22,18 @@ import { SelectionManager } from '../client/util/SelectionManager'; import { DateField } from '../new_fields/DateField'; import { GestureUtils } from '../pen-gestures/GestureUtils'; import { DocServer } from '../client/DocServer'; +import { DocumentDecorations } from '../client/views/DocumentDecorations'; +import { OverlayView } from '../client/views/OverlayView'; +import { DictationOverlay } from '../client/views/DictationOverlay'; +import SharingManager from '../client/util/SharingManager'; +import { PreviewCursor } from '../client/views/PreviewCursor'; +import { ContextMenu } from '../client/views/ContextMenu'; +import { RadialMenu } from '../client/views/nodes/RadialMenu'; +import PDFMenu from '../client/views/pdf/PDFMenu'; +import MarqueeOptionsMenu from '../client/views/collections/collectionFreeForm/MarqueeOptionsMenu'; +import GoogleAuthenticationManager from '../client/apis/GoogleAuthenticationManager'; +import { listSpec } from '../new_fields/Schema'; +import { Id } from '../new_fields/FieldSymbols'; library.add(faLongArrowAltLeft); @@ -31,7 +43,7 @@ export default class MobileInterface extends React.Component { @computed private get userDoc() { return CurrentUserUtils.UserDocument; } @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; } // @observable private currentView: "main" | "ink" | "upload" = "main"; - private mainDoc: Doc = CurrentUserUtils.setupMobileDoc(this.userDoc); + private mainDoc: any = CurrentUserUtils.setupMobileDoc(this.userDoc); @observable private renderView?: () => JSX.Element; // private inkDoc?: Doc; @@ -49,7 +61,6 @@ export default class MobileInterface extends React.Component { library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]); if (this.userDoc && !this.mainContainer) { - // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); this.userDoc.activeMobile = this.mainDoc; } } @@ -76,48 +87,22 @@ export default class MobileInterface extends React.Component { }); } - // @action - // switchCurrentView = (view: "main" | "ink" | "upload") => { - // this.currentView = view; - - // if (this.userDoc) { - // switch (view) { - // case "main": { - // // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); - // this.userDoc.activeMobile = this.mainDoc; - // break; - // } - // case "ink": { - // this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc); - // this.userDoc.activeMobile = this.inkDoc; - // InkingControl.Instance.switchTool(InkTool.Pen); - // this.drawingInk = true; - - // DocServer.Mobile.dispatchOverlayTrigger({ - // enableOverlay: true, - // width: window.innerWidth, - // height: window.innerHeight - // }); - - // break; - // } - // case "upload": { - // this.uploadDoc = CurrentUserUtils.setupMobileUploadDoc(this.userDoc); - // this.userDoc.activeMobile = this.uploadDoc; - - // } - // } - // } - // } + onSwitchUpload = () => { + DocServer.Mobile.dispatchOverlayTrigger({ + enableOverlay: true, + width: 100, + height: 100 + }); + } renderDefaultContent = () => { - console.log("rendering default content"); + console.log("rendering default content", this.mainContainer); if (this.mainContainer) { return { console.log("want to add doc to default content", StrCast(doc.title)); return false; }} addDocTab={returnFalse} pinToPres={emptyFunction} removeDocument={undefined} @@ -192,42 +177,54 @@ export default class MobileInterface extends React.Component {
- - window.innerHeight} - PanelWidth={() => window.innerWidth} - focus={emptyFunction} - isSelected={returnFalse} - select={emptyFunction} - active={returnFalse} - ContentScaling={returnOne} - whenActiveChanged={returnFalse} - ScreenToLocalTransform={Transform.Identity} - ruleProvider={undefined} - renderDepth={0} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined}> - - + window.innerHeight} + PanelWidth={() => window.innerWidth} + focus={emptyFunction} + isSelected={returnFalse} + select={emptyFunction} + active={returnFalse} + ContentScaling={returnOne} + whenActiveChanged={returnFalse} + ScreenToLocalTransform={Transform.Identity} + ruleProvider={undefined} + renderDepth={0} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined}> +
); } } - upload = () => { + upload = async (e: React.MouseEvent) => { + if (this.mainContainer) { + const data = Cast(this.mainContainer.data, listSpec(Doc)); + if (data) { + const uploadDoc = await data[1]; // TODO: ensure this is the collection to upload + console.log("UPLOADING DOCUMENT FROM MOBILE", uploadDoc[Id], StrCast(uploadDoc.proto!.title)); + if (uploadDoc) { + DocServer.Mobile.dispatchMobileDocumentUpload({ + docId: uploadDoc[Id] + }); + } + } + } + e.stopPropagation(); + e.preventDefault(); } renderUploadContent() { if (this.mainContainer) { return ( -
+
@@ -240,7 +237,7 @@ export default class MobileInterface extends React.Component { Document={this.mainContainer} DataDoc={undefined} LibraryPath={emptyPath} - addDocument={returnFalse} + addDocument={(doc: Doc) => { console.log("want to add doc", StrCast(doc.title)); return false; }} addDocTab={returnFalse} pinToPres={emptyFunction} removeDocument={undefined} @@ -266,13 +263,35 @@ export default class MobileInterface extends React.Component { } } + onDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + } + render() { // const content = this.currentView === "main" ? this.mainContent : // this.currentView === "ink" ? this.inkContent : // this.currentView === "upload" ? this.uploadContent : <>; return ( -
- {this.renderView ? this.renderView() : this.renderDefaultContent()} +
+ {/* + + {this.renderView ? this.renderView() : this.renderDefaultContent()} + */} + + {/* + + */} + + + {this.renderView ? this.renderView() : this.renderDefaultContent()} + + + {/* */} + + {/* + + */}
); } @@ -281,5 +300,6 @@ export default class MobileInterface extends React.Component { Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); }); Scripting.addGlobal(function onSwitchMobileInking() { return MobileInterface.Instance.onSwitchInking(); }); Scripting.addGlobal(function renderMobileInking() { return MobileInterface.Instance.renderInkingContent(); }); +Scripting.addGlobal(function onSwitchMobileUpload() { return MobileInterface.Instance.onSwitchUpload(); }); Scripting.addGlobal(function renderMobileUpload() { return MobileInterface.Instance.renderUploadContent(); }); diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 74f45ae62..e76d9b7a2 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -42,6 +42,7 @@ export default class UploadManager extends ApiManager { method: Method.POST, subscription: "/upload", secureHandler: async ({ req, res }) => { + console.log("/upload register"); const form = new formidable.IncomingForm(); form.uploadDir = pathToDirectory(Directory.parsed_files); form.keepExtensions = true; diff --git a/src/server/Message.ts b/src/server/Message.ts index 236df3f3c..c23a2f0a8 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,5 +1,6 @@ import { Utils } from "../Utils"; import { Point } from "../pen-gestures/ndollar"; +import { Doc } from "../new_fields/Doc"; export class Message { private _name: string; @@ -62,6 +63,10 @@ export interface UpdateMobileInkOverlayPositionContent { readonly dsize?: number; } +export interface MobileDocumentUploadContent { + readonly docId: string; +} + export namespace MessageStore { export const Foo = new Message("Foo"); export const Bar = new Message("Bar"); @@ -75,6 +80,7 @@ export namespace MessageStore { export const GesturePoints = new Message("Gesture Points"); export const MobileInkOverlayTrigger = new Message("Trigger Mobile Ink Overlay"); export const UpdateMobileInkOverlayPosition = new Message("Update Mobile Ink Overlay Position"); + export const MobileDocumentUpload = new Message("Upload Document From Mobile"); export const GetRefField = new Message("Get Ref Field"); export const GetRefFields = new Message("Get Ref Fields"); diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index 77816c897..798bdae67 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -1,5 +1,5 @@ import { Utils } from "../../Utils"; -import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from "../Message"; +import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../Message"; import { Client } from "../Client"; import { Socket } from "socket.io"; import { Database } from "../database"; @@ -57,6 +57,7 @@ export namespace WebSocket { Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content)); Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content)); Utils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content)); + Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content)); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField); Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields); @@ -83,6 +84,10 @@ export namespace WebSocket { socket.broadcast.emit("receiveUpdateOverlayPosition", content); } + function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) { + socket.broadcast.emit("receiveMobileDocumentUpload", content); + } + function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) { const { ProjectCredentials } = GoogleCredentialsLoader; switch (query.type) { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 817cf40b1..3e5953ac1 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -103,7 +103,7 @@ export class CurrentUserUtils { { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc }, { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc }, { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "red", activePen: doc }, - { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload);', backgroundColor: "orange" }, + { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" }, { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" }, ]; return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({ @@ -138,7 +138,9 @@ export class CurrentUserUtils { } static setupMobileDoc(userDoc: Doc) { - return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), { + const webDoc = Docs.Create.WebDocument("https://wikipedia.com", { title: "Mobile Upload Web", chromeStatus: "enabled" }); + + return userDoc.activeMoble ?? Docs.Create.MasonryDocument([webDoc, ...CurrentUserUtils.setupMobileButtons(userDoc)], { columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5 }); } @@ -148,13 +150,14 @@ export class CurrentUserUtils { } static setupMobileUploadDoc(userDoc: Doc) { - console.log("setup mobile upload", window.innerWidth, window.innerHeight); - const webDoc = Docs.Create.WebDocument("https://wikipedia.com", { title: "Mobile Upload Web", chromeStatus: "enabled" }); - const uploadDoc = Docs.Create.StackingDocument([], { title: "Mobile Upload", backgroundColor: "pink" }); - return Docs.Create.StackingDocument([webDoc, uploadDoc], { - title: "Mobile Upload", backgroundColor: "white", - columnWidth: window.innerWidth, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", autoHeight: true, yMargin: 5, - width: window.innerWidth, height: window.innerHeight + const webDoc = Docs.Create.WebDocument("https://yahoo.com", { title: "Upload Images From the Web", chromeStatus: "enabled" }); + const uploadDoc = Docs.Create.StackingDocument([], { title: "Mobile Upload Collection", backgroundColor: "pink" }); + console.log("window size", window.innerWidth, window.innerHeight); + // return Docs.Create.StackingDocument([webDoc, uploadDoc], { + // columnWidth: window.innerWidth, //ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "Mobile Upload", autoHeight: true, yMargin: 5 + // }); + return Docs.Create.StackingDocument([webDoc, uploadDoc], {//...CurrentUserUtils.setupMobileButtons(userDoc)], { + columnWidth: 100, lockedPosition: true, chromeStatus: "disabled", title: "Upload", autoHeight: true, yMargin: 30 }); } diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index cbe070293..5f1ecc733 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -42,18 +42,18 @@ export default async function InitializeServer(routeSetter: RouteSetter) { } }; app.use(cors(corsOptions)); - app.use("*", ({ user, originalUrl }, res, next) => { - if (user && !originalUrl.includes("Heartbeat")) { - const userEmail = (user as any).email; - if (userEmail) { - timeMap[userEmail] = Date.now(); - } - } - if (!user && originalUrl === "/") { - return res.redirect("/login"); - } - next(); - }); + // app.use("*", ({ user, originalUrl }, res, next) => { + // if (user && !originalUrl.includes("Heartbeat")) { + // const userEmail = (user as any).email; + // if (userEmail) { + // timeMap[userEmail] = Date.now(); + // } + // } + // if (!user && originalUrl === "/") { + // return res.redirect("/login"); + // } + // next(); + // }); app.use(wdm(compiler, { publicPath: config.output.publicPath })); app.use(whm(compiler)); -- cgit v1.2.3-70-g09d2 From b6c45582962201b843b1a414fb29d16ec25bdaaf Mon Sep 17 00:00:00 2001 From: andrewdkim Date: Sun, 9 Feb 2020 19:07:01 -0500 Subject: bug fixed the radial menu --- src/client/views/GestureOverlay.tsx | 68 ++++++++++++++++++++---------- src/client/views/nodes/DocumentView.tsx | 33 +++++++++------ src/client/views/nodes/RadialMenu.tsx | 75 ++++++++++++++++++--------------- 3 files changed, 106 insertions(+), 70 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index b85f35fe4..a2d57a69c 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -27,6 +27,7 @@ import { listSpec } from "../../new_fields/Schema"; import { List } from "../../new_fields/List"; import { CollectionViewType } from "./collections/CollectionView"; import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; +import { RadialMenu } from "./nodes/RadialMenu"; @observer export default class GestureOverlay extends Touchable { @@ -104,6 +105,16 @@ export default class GestureOverlay extends Touchable { } onReactTouchStart = (te: React.TouchEvent) => { + console.log("STARTING"); + console.log(te.touches); + // if (RadialMenu.Instance._display === true) { + // console.log("display", RadialMenu.Instance._display); + // runInAction(() => { RadialMenu.Instance._display = false; }); + // te.preventDefault(); + // te.stopPropagation(); + // return; + // } + const actualPts: React.Touch[] = []; for (let i = 0; i < te.touches.length; i++) { const pt: any = te.touches.item(i); @@ -127,9 +138,10 @@ export default class GestureOverlay extends Touchable { ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); const nts = this.getNewTouches(te); - + console.log(this.prevPoints.size, nts.ntt.length, nts.nt.length, nts.nct.length); if (nts.nt.length < 5) { const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); + console.log(te.touches); target?.dispatchEvent( new CustomEvent>("dashOnTouchStart", { @@ -144,33 +156,41 @@ export default class GestureOverlay extends Touchable { } ) ); - if (this.prevPoints.size === 1 && this._holdTimer === undefined) { + console.log("2"); + if (nts.nt.length === 1) { console.log("started"); this._holdTimer = setTimeout(() => { console.log("hold"); const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); - target?.dispatchEvent( - new CustomEvent>("dashOnTouchHoldStart", - { - bubbles: true, - detail: { - fingers: this.prevPoints.size, - targetTouches: nts.ntt, - touches: nts.nt, - changedTouches: nts.nct, - touchEvent: te + console.log(this.prevPoints.size, nts.ntt.length, nts.nt.length, nts.nct.length); + if (nts.nt.length === 1) { + target?.dispatchEvent( + new CustomEvent>("dashOnTouchHoldStart", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: te + } } - } - ) - ); - this._holdTimer = undefined; - document.removeEventListener("touchmove", this.onReactTouchMove); - document.removeEventListener("touchend", this.onReactTouchEnd); - document.removeEventListener("touchmove", this.onReactHoldTouchMove); - document.removeEventListener("touchend", this.onReactHoldTouchEnd); - document.addEventListener("touchmove", this.onReactHoldTouchMove); - document.addEventListener("touchend", this.onReactHoldTouchEnd); - }, (1000)); + ) + ); + this._holdTimer = undefined; + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + document.removeEventListener("touchmove", this.onReactHoldTouchMove); + document.removeEventListener("touchend", this.onReactHoldTouchEnd); + document.addEventListener("touchmove", this.onReactHoldTouchMove); + document.addEventListener("touchend", this.onReactHoldTouchEnd); + } + + }, (500)); + } + else { + clearTimeout(this._holdTimer); } document.removeEventListener("touchmove", this.onReactTouchMove); document.removeEventListener("touchend", this.onReactTouchEnd); @@ -298,6 +318,7 @@ export default class GestureOverlay extends Touchable { } handleHandDown = async (e: React.TouchEvent) => { + clearTimeout(this._holdTimer!); const fingers = new Array(); for (let i = 0; i < e.touches.length; i++) { const pt: any = e.touches.item(i); @@ -347,6 +368,7 @@ export default class GestureOverlay extends Touchable { const thumbDoc = await Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc); if (thumbDoc) { runInAction(() => { + RadialMenu.Instance._display = false; this._inkToTextDoc = FieldValue(Cast(thumbDoc.inkToTextDoc, Doc)); this._thumbDoc = thumbDoc; this._thumbX = thumb.clientX; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6b400113b..d4e3546f7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -124,24 +124,27 @@ export class DocumentView extends DocComponent(Docu private _firstY: number = -1; + handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { - this.addHoldMoveListeners(); - this.addHoldEndListeners(); - this.onRadialMenu(e, me); - const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - this._firstX = pt.pageX; - this._firstY = pt.pageY; + console.log("START"); + if (RadialMenu.Instance._display === false) { + this.addHoldMoveListeners(); + this.addHoldEndListeners(); + this.onRadialMenu(e, me); + const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; + this._firstX = pt.pageX; + this._firstY = pt.pageY; + } } handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - console.log(pt.pageX, this._firstX, pt.pageY, this._firstY); + const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; + if (this._firstX === -1 || this._firstY === -1) { return; } if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { - console.log("WHY"); this.handle1PointerHoldEnd(e, me); } } @@ -154,20 +157,22 @@ export class DocumentView extends DocComponent(Docu @action onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - - RadialMenu.Instance.openMenu(); + console.log("DISPLAYMENUUUU"); + console.log(me.touchEvent.touches); + // console.log(InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)); + // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; + RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15); RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 }); RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 }); RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 }); RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); - RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15); if (!SelectionManager.IsSelected(this, true)) { SelectionManager.SelectDoc(this, false); } - e.stopPropagation(); + } @action diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx index 74c5f53bd..a6fb72a7b 100644 --- a/src/client/views/nodes/RadialMenu.tsx +++ b/src/client/views/nodes/RadialMenu.tsx @@ -24,12 +24,19 @@ export class RadialMenu extends React.Component { private _reactionDisposer?: IReactionDisposer; + catchTouch = (te: React.TouchEvent) => { + console.log("caught"); + te.stopPropagation(); + te.preventDefault(); + } + @action onPointerDown = (e: PointerEvent) => { this._mouseDown = true; this._mouseX = e.clientX; this._mouseY = e.clientY; document.addEventListener("pointermove", this.onPointerMove); + } @observable @@ -42,7 +49,6 @@ export class RadialMenu extends React.Component { const deltX = this._mouseX - curX; const deltY = this._mouseY - curY; const scale = Math.hypot(deltY, deltX); - if (scale < 150 && scale > 50) { const rad = Math.atan2(deltY, deltX) + Math.PI; let closest = 0; @@ -83,6 +89,7 @@ export class RadialMenu extends React.Component { @action componentDidMount = () => { + console.log(this._pageX); document.addEventListener("pointerdown", this.onPointerDown); document.addEventListener("pointerup", this.onPointerUp); this.previewcircle(); @@ -98,7 +105,7 @@ export class RadialMenu extends React.Component { @observable private _pageX: number = 0; @observable private _pageY: number = 0; - @observable private _display: boolean = false; + @observable _display: boolean = false; @observable private _yRelativeToTop: boolean = true; @@ -124,35 +131,34 @@ export class RadialMenu extends React.Component { displayMenu = (x: number, y: number) => { //maxX and maxY will change if the UI/font size changes, but will work for any amount //of items added to the menu - - this._pageX = x; - this._pageY = y; + this._mouseX = x; + this._mouseY = y; this._shouldDisplay = true; } - - get pageX() { - const x = this._pageX; - if (x < 0) { - return 0; - } - const width = this._width; - if (x + width > window.innerWidth - RadialMenu.buffer) { - return window.innerWidth - RadialMenu.buffer - width; - } - return x; - } - - get pageY() { - const y = this._pageY; - if (y < 0) { - return 0; - } - const height = this._height; - if (y + height > window.innerHeight - RadialMenu.buffer) { - return window.innerHeight - RadialMenu.buffer - height; - } - return y; - } + // @computed + // get pageX() { + // const x = this._pageX; + // if (x < 0) { + // return 0; + // } + // const width = this._width; + // if (x + width > window.innerWidth - RadialMenu.buffer) { + // return window.innerWidth - RadialMenu.buffer - width; + // } + // return x; + // } + // @computed + // get pageY() { + // const y = this._pageY; + // if (y < 0) { + // return 0; + // } + // const height = this._height; + // if (y + height > window.innerHeight - RadialMenu.buffer) { + // return window.innerHeight - RadialMenu.buffer - height; + // } + // return y; + // } @computed get menuItems() { return this._items.map((item, index) => ); @@ -166,7 +172,10 @@ export class RadialMenu extends React.Component { } @action - openMenu = () => { + openMenu = (x: number, y: number) => { + + this._pageX = x; + this._pageY = y; this._shouldDisplay; this._display = true; } @@ -207,12 +216,12 @@ export class RadialMenu extends React.Component { if (!this._display) { return null; } - const style = this._yRelativeToTop ? { left: this._mouseX - 150, top: this._mouseY - 150 } : - { left: this._mouseX - 150, top: this._mouseY - 150 }; + const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } : + { left: this._pageX - 130, top: this._pageY - 130 }; return ( -
+
Your browser does not support the HTML5 canvas tag. {this.menuItems}
-- cgit v1.2.3-70-g09d2 From dba26063754e6a882c8a3eb238b04a935989f406 Mon Sep 17 00:00:00 2001 From: kimdahey Date: Mon, 10 Feb 2020 04:52:04 -0500 Subject: started long press for iframe to copy image --- src/client/util/DragManager.ts | 6 +- src/client/views/collections/CollectionSubView.tsx | 2 + src/client/views/nodes/WebBox.tsx | 114 ++++++++++++++++++++- 3 files changed, 119 insertions(+), 3 deletions(-) (limited to 'src/client/views') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 0bb8b531d..745540ff9 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -179,7 +179,7 @@ export namespace DragManager { ); } element.dataset.canDrop = "true"; - const handler = (e: Event) => { console.log("drop target reveied docs"); dropFunc(e, (e as CustomEvent).detail); }; + const handler = (e: Event) => { dropFunc(e, (e as CustomEvent).detail); }; element.addEventListener("dashOnDrop", handler); return () => { element.removeEventListener("dashOnDrop", handler); @@ -265,6 +265,10 @@ export namespace DragManager { StartDrag([ele], dragData, downX, downY, options); } + export function StartImgDrag(ele: HTMLElement, downX: number, downY: number) { + StartDrag([ele], {}, downX, downY); + } + function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { eles = eles.filter(e => e); if (!dragDiv) { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index b35af2314..b5067ea9e 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -180,6 +180,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { } const html = e.dataTransfer.getData("text/html"); const text = e.dataTransfer.getData("text/plain"); + console.log(html); if (text && text.startsWith("(schemaCtor: (doc: Doc) => T) { const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : ""; if (img) { const split = img.split("src=\"")[1].split("\"")[0]; + console.log("creating image", split); const doc = Docs.Create.ImageDocument(split, { ...options, width: 300 }); ImageUtils.ExtractExif(doc); this.props.addDocument(doc); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 0b23c3bec..c405dd9d4 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -21,6 +21,9 @@ import "./WebBox.scss"; import React = require("react"); import { DocAnnotatableComponent } from "../DocComponent"; import { documentSchema } from "../../../new_fields/documentSchemas"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { DragManager } from "../../util/DragManager"; +import { ImageUtils } from "../../util/Import & Export/ImageUtils"; library.add(faStickyNote); @@ -34,6 +37,12 @@ export class WebBox extends DocAnnotatableComponent @observable private collapsed: boolean = true; @observable private url: string = ""; + private _longPressSecondsHack?: NodeJS.Timeout; + private _iframeRef = React.createRef(); + private _iframeDragRef = React.createRef(); + @observable private _pressX: number = 0; + @observable private _pressY: number = 0; + componentWillMount() { const field = Cast(this.props.Document[this.props.fieldKey], WebField); @@ -51,6 +60,49 @@ export class WebBox extends DocAnnotatableComponent this.setURL(); } + componentDidMount() { + document.addEventListener("pointerup", this.onLongPressUp); + document.addEventListener("pointermove", this.onLongPressMove); + // this._iframeRef.current?.contentWindow?.document.addEventListener("mousedown", (event: MouseEvent) => { + // console.log("clicked inside the iframe?"); + // }); + // const iframe = document.getElementById(this.props.Document.proto![Id]); + // if (iframe) { + // iframe.addEventListener('pointerdown', function (event) { + // const B = iframe.getBoundingClientRect(); + // const e = new CustomEvent('pointerdown', { bubbles: true, cancelable: false }); + // // e.clientX = e.clientX + B?.left; + // // e.clientY = e.clientY + B?.top; + // console.log("custom event pointer down"); + // iframe.dispatchEvent(e); + // }) + // } + // if (this._iframeRef.current) { + // console.log("resetting iframes events"); + // const self = this; + // this._iframeRef.current.addEventListener('pointermove', function (event) { + // const B = self._iframeRef.current?.getBoundingClientRect(); + // const e = new CustomEvent('pointermove', { bubbles: true, cancelable: false }); + // // e.clientX = e.clientX + B?.left; + // // e.clientY = e.clientY + B?.top; + // self._iframeRef.current?.dispatchEvent(e); + // }); + // this._iframeRef.current.addEventListener('pointerdown', function (event) { + // const B = self._iframeRef.current?.getBoundingClientRect(); + // const e = new CustomEvent('pointerdown', { bubbles: true, cancelable: false }); + // // e.clientX = e.clientX + B?.left; + // // e.clientY = e.clientY + B?.top; + // console.log("custom event pointer down"); + // self._iframeRef.current?.dispatchEvent(e); + // }) + // } + } + + componentWillUnmount() { + document.removeEventListener("pointerup", this.onLongPressUp); + document.removeEventListener("pointermove", this.onLongPressMove); + } + @action onURLChange = (e: React.ChangeEvent) => { this.url = e.target.value; @@ -165,6 +217,62 @@ export class WebBox extends DocAnnotatableComponent } } + // TODO: make this actually a long press + onLongPressDown = (e: React.PointerEvent) => { + + console.log("press down", e.clientX, e.clientX); + this._pressX = e.clientX; + this._pressY = e.clientY; + this._longPressSecondsHack = setTimeout(() => { + console.log("start the drag!!"); + const B = this._iframeRef.current?.getBoundingClientRect(); + const iframeDoc = this._iframeRef.current?.contentDocument; + if (B && iframeDoc) { + console.log("frame doc", iframeDoc); + console.log("get point", this._pressX, B.left, this._pressY, B.top); + const element = iframeDoc.elementFromPoint(this._pressX - B.left, this._pressY - B.top); + console.log("found element", element); + if (element) { + e.stopPropagation(); + e.preventDefault(); + const clone = element.cloneNode(true) as HTMLElement; + + if (clone.nodeName === "IMG") { + const src = clone.getAttribute("src"); // TODO: may not always work + + if (src) { + const doc = Docs.Create.ImageDocument(src, { width: 300 }); + ImageUtils.ExtractExif(doc); + + console.log("start image drag", this._pressX, this._pressY, doc); + // document.dispatchEvent() + const dragData = new DragManager.DocumentDragData([doc]); + DragManager.StartDocumentDrag([clone], dragData, this._pressX, this._pressY); + } + } + } + } + }, 1500); + // e.stopPropagation(); + // e.preventDefault(); + } + + onLongPressMove = (e: PointerEvent) => { + this._pressX = e.clientX; + this._pressY = e.clientY; + } + + onLongPressUp = (e: PointerEvent) => { + console.log("press up"); + if (this._longPressSecondsHack) { + clearTimeout(this._longPressSecondsHack); + console.log("long press cancelled"); + } + // e.stopPropagation(); + // e.preventDefault(); + } + + @computed get content() { const field = this.dataDoc[this.props.fieldKey]; @@ -172,9 +280,9 @@ export class WebBox extends DocAnnotatableComponent if (field instanceof HtmlField) { view = ; } else if (field instanceof WebField) { - view =