From e0316c21838613df0fbf43df6a9ca5d696c52f47 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 30 Jul 2019 20:19:58 -0400 Subject: more interesting speech commands and command manager pattern for DictationManager --- src/client/views/nodes/DocumentView.tsx | 48 ++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index dc56c1c8f..00416ca42 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -43,6 +43,7 @@ import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-i import { DocumentDecorations } from '../DocumentDecorations'; import { CognitiveServices } from '../../cognitive_services/CognitiveServices'; import DictationManager from '../../util/DictationManager'; +import { CollectionViewType } from '../collections/CollectionBaseView'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -153,6 +154,43 @@ export class DocumentView extends DocComponent(Docu constructor(props: DocumentViewProps) { super(props); + let fixed = DictationManager.Instance.registerStatic; + let dynamic = DictationManager.Instance.registerDynamic; + fixed(["Open Fields"], DocumentView.OpenFieldsDictation); + fixed(["Clear"], DocumentView.ClearChildren); + dynamic(/create (\w+) documents of type (image|nested collection)/g, DocumentView.BuildLayout); + dynamic(/view as (freeform|stacking|masonry|schema|tree)/g, DocumentView.SetViewMode); + } + + public static ClearChildren = (target: DocumentView) => { + Doc.GetProto(target.props.Document).data = new List(); + } + + public static BuildLayout = (target: DocumentView, matches: RegExpExecArray) => { + let count = DictationManager.Instance.interpretNumber(matches[1]); + let what = matches[2]; + if (!("viewType" in target.props.Document)) { + return; + } + let dataDoc = Doc.GetProto(target.props.Document); + let fieldKey = "data"; + for (let i = 0; i < count; i++) { + let created: Doc | undefined; + switch (what) { + case "image": + created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); + break; + case "nested collection": + created = Docs.Create.FreeformDocument([], {}); + break; + } + created && Doc.AddDocToList(dataDoc, fieldKey, created); + } + } + + public static SetViewMode = (target: DocumentView, matches: RegExpExecArray) => { + let mode = CollectionViewType.ValueOf(matches[1]); + mode && (target.props.Document.viewType = mode); } _animateToIconDisposer?: IReactionDisposer; @@ -413,7 +451,15 @@ export class DocumentView extends DocComponent(Docu deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); } @undoBatch - fieldsClicked = (): void => { let kvp = Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.dataDoc, "onRight"); } + fieldsClicked = (): void => { + let kvp = Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }); + this.props.addDocTab(kvp, this.dataDoc, "onRight"); + } + + public static OpenFieldsDictation = (target: DocumentView) => { + let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); + target.props.addDocTab(kvp, target.dataDoc, "onRight"); + } @undoBatch makeBtnClicked = (): void => { -- cgit v1.2.3-70-g09d2 From 31a8bb32e696d58816329b66cfc5b92907494d1b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 30 Jul 2019 23:29:23 -0400 Subject: refactor --- src/client/util/DictationManager.ts | 88 +++++++++++++++++++++++++++------ src/client/views/nodes/DocumentView.tsx | 46 ----------------- 2 files changed, 72 insertions(+), 62 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 60b25afc5..6d67f6d6d 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -4,6 +4,10 @@ import { SelectionManager } from "./SelectionManager"; import { DocumentView } from "../views/nodes/DocumentView"; import { UndoManager } from "./UndoManager"; import * as converter from "words-to-numbers"; +import { Doc } from "../../new_fields/Doc"; +import { List } from "../../new_fields/List"; +import { Docs } from "../documents/Documents"; +import { CollectionViewType } from "../views/collections/CollectionBaseView"; namespace CORE { export interface IWindow extends Window { @@ -12,14 +16,12 @@ namespace CORE { } const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; -export type Action = (target: DocumentView) => any | Promise; -export type DynamicAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; -export type RegexEntry = { key: RegExp, value: DynamicAction }; +export type IndependentAction = (target: DocumentView) => any | Promise; +export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; +export type RegexEntry = { key: RegExp, value: DependentAction }; export default class DictationManager { public static Instance = new DictationManager(); - private registeredCommands = new Map(); - private registeredRegexes: RegexEntry[] = []; private isListening = false; private recognizer: any; @@ -60,13 +62,13 @@ export default class DictationManager { return title.replace("...", "").toLowerCase().trim(); } - public registerStatic = (keys: Array, action: Action, overwrite = false) => { + public registerStatic = (keys: Array, action: IndependentAction, overwrite = false) => { let success = true; keys.forEach(key => { key = this.sanitize(key); - let existing = this.registeredCommands.get(key); + let existing = RegisteredCommands.Independent.get(key); if (!existing || overwrite) { - this.registeredCommands.set(key, action); + RegisteredCommands.Independent.set(key, action); } else { success = false; } @@ -86,8 +88,8 @@ export default class DictationManager { return typeof converted === "string" ? parseInt(converted) : converted; } - public registerDynamic = (dynamicKey: RegExp, action: DynamicAction) => { - this.registeredRegexes.push({ + public registerDynamic = (dynamicKey: RegExp, action: DependentAction) => { + RegisteredCommands.Dependent.push({ key: dynamicKey, value: action }); @@ -101,20 +103,20 @@ export default class DictationManager { let batch = UndoManager.StartBatch("Dictation Action"); phrase = this.sanitize(phrase); - let registeredAction = this.registeredCommands.get(phrase); - if (registeredAction) { - await registeredAction(target); + let independentAction = RegisteredCommands.Independent.get(phrase); + if (independentAction) { + await independentAction(target); return true; } let success = false; - for (let entry of this.registeredRegexes) { + for (let entry of RegisteredCommands.Dependent) { let regex = entry.key; - let registeredDynamicAction = entry.value; + let dependentAction = entry.value; let matches = regex.exec(phrase); regex.lastIndex = 0; if (matches !== null) { - await registeredDynamicAction(target, matches); + await dependentAction(target, matches); success = true; break; } @@ -124,4 +126,58 @@ export default class DictationManager { return success; } +} + +export namespace RegisteredCommands { + + export const Independent = new Map([ + + ["clear", (target: DocumentView) => { + Doc.GetProto(target.props.Document).data = new List(); + }], + + ["open fields", (target: DocumentView) => { + let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); + target.props.addDocTab(kvp, target.dataDoc, "onRight"); + }] + + ]); + + export const Dependent = new Array( + + { + key: /create (\w+) documents of type (image|nested collection)/g, + value: (target: DocumentView, matches: RegExpExecArray) => { + let count = DictationManager.Instance.interpretNumber(matches[1]); + let what = matches[2]; + if (!("viewType" in target.props.Document)) { + return; + } + let dataDoc = Doc.GetProto(target.props.Document); + let fieldKey = "data"; + for (let i = 0; i < count; i++) { + let created: Doc | undefined; + switch (what) { + case "image": + created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); + break; + case "nested collection": + created = Docs.Create.FreeformDocument([], {}); + break; + } + created && Doc.AddDocToList(dataDoc, fieldKey, created); + } + } + }, + + { + key: /view as (freeform|stacking|masonry|schema|tree)/g, + value: (target: DocumentView, matches: RegExpExecArray) => { + let mode = CollectionViewType.ValueOf(matches[1]); + mode && (target.props.Document.viewType = mode); + } + } + + ); + } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 00416ca42..db8203167 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -152,47 +152,6 @@ export class DocumentView extends DocComponent(Docu set templates(templates: List) { this.props.Document.templates = templates; } screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); - constructor(props: DocumentViewProps) { - super(props); - let fixed = DictationManager.Instance.registerStatic; - let dynamic = DictationManager.Instance.registerDynamic; - fixed(["Open Fields"], DocumentView.OpenFieldsDictation); - fixed(["Clear"], DocumentView.ClearChildren); - dynamic(/create (\w+) documents of type (image|nested collection)/g, DocumentView.BuildLayout); - dynamic(/view as (freeform|stacking|masonry|schema|tree)/g, DocumentView.SetViewMode); - } - - public static ClearChildren = (target: DocumentView) => { - Doc.GetProto(target.props.Document).data = new List(); - } - - public static BuildLayout = (target: DocumentView, matches: RegExpExecArray) => { - let count = DictationManager.Instance.interpretNumber(matches[1]); - let what = matches[2]; - if (!("viewType" in target.props.Document)) { - return; - } - let dataDoc = Doc.GetProto(target.props.Document); - let fieldKey = "data"; - for (let i = 0; i < count; i++) { - let created: Doc | undefined; - switch (what) { - case "image": - created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); - break; - case "nested collection": - created = Docs.Create.FreeformDocument([], {}); - break; - } - created && Doc.AddDocToList(dataDoc, fieldKey, created); - } - } - - public static SetViewMode = (target: DocumentView, matches: RegExpExecArray) => { - let mode = CollectionViewType.ValueOf(matches[1]); - mode && (target.props.Document.viewType = mode); - } - _animateToIconDisposer?: IReactionDisposer; _reactionDisposer?: IReactionDisposer; @action @@ -456,11 +415,6 @@ export class DocumentView extends DocComponent(Docu this.props.addDocTab(kvp, this.dataDoc, "onRight"); } - public static OpenFieldsDictation = (target: DocumentView) => { - let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); - target.props.addDocTab(kvp, target.dataDoc, "onRight"); - } - @undoBatch makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); -- cgit v1.2.3-70-g09d2 From ec224416fc454c7fdbb62943408226c973d8c751 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 1 Aug 2019 02:30:47 -0400 Subject: fixed import ordering issue, allow for multiple targets and valid document types --- src/client/documents/Documents.ts | 45 ++++++++------- src/client/util/DictationManager.ts | 66 ++++++++-------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 --- src/client/views/nodes/DocumentView.tsx | 1 - src/client/views/search/FilterBox.tsx | 2 +- src/client/views/search/SearchItem.tsx | 2 +- src/client/views/search/ToggleBar.tsx | 2 +- 7 files changed, 49 insertions(+), 77 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2a1f63d59..3c7de17c8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,3 +1,24 @@ +export enum DocumentType { + NONE = "none", + TEXT = "text", + HIST = "histogram", + IMG = "image", + WEB = "web", + COL = "collection", + KVP = "kvp", + VID = "video", + AUDIO = "audio", + PDF = "pdf", + ICON = "icon", + IMPORT = "import", + LINK = "link", + LINKDOC = "linkdoc", + BUTTON = "button", + TEMPLATE = "template", + EXTENSION = "extension", + YOUTUBE = "youtube", +} + import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { HistogramBox } from "../northstar/dash-nodes/HistogramBox"; import { HistogramOperation } from "../northstar/operations/HistogramOperation"; @@ -25,14 +46,13 @@ import { OmitKeys, JSONUtils } from "../../Utils"; import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField"; import { HtmlField } from "../../new_fields/HtmlField"; import { List } from "../../new_fields/List"; -import { Cast, NumCast, StrCast, ToConstructor, InterfaceValue, FieldValue } from "../../new_fields/Types"; +import { Cast, NumCast } from "../../new_fields/Types"; import { IconField } from "../../new_fields/IconField"; import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; import { dropActionType } from "../util/DragManager"; import { DateField } from "../../new_fields/DateField"; import { UndoManager } from "../util/UndoManager"; -import { RouteStore } from "../../server/RouteStore"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { LinkManager } from "../util/LinkManager"; @@ -44,27 +64,6 @@ import { SchemaHeaderField, RandomPastel } from "../../new_fields/SchemaHeaderFi var requestImageSize = require('../util/request-image-size'); var path = require('path'); -export enum DocumentType { - NONE = "none", - TEXT = "text", - HIST = "histogram", - IMG = "image", - WEB = "web", - COL = "collection", - KVP = "kvp", - VID = "video", - AUDIO = "audio", - PDF = "pdf", - ICON = "icon", - IMPORT = "import", - LINK = "link", - LINKDOC = "linkdoc", - BUTTON = "button", - TEMPLATE = "template", - EXTENSION = "extension", - YOUTUBE = "youtube", -} - export interface DocumentOptions { x?: number; y?: number; diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 49afe5371..b0866a826 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -6,9 +6,9 @@ import { Doc } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs, DocumentType } from "../documents/Documents"; import { CollectionViewType } from "../views/collections/CollectionBaseView"; -import { listSpec } from "../../new_fields/Schema"; import { Cast, CastCtor } from "../../new_fields/Types"; -import { ImageField, AudioField } from "../../new_fields/URLField"; +import { listSpec } from "../../new_fields/Schema"; +import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; namespace CORE { @@ -17,7 +17,7 @@ namespace CORE { } } -const Mapping = new Map([ +const ConstructorMap = new Map([ [DocumentType.COL, listSpec(Doc)], [DocumentType.AUDIO, AudioField], [DocumentType.IMG, ImageField], @@ -26,7 +26,7 @@ const Mapping = new Map([ ]); const tryCast = (view: DocumentView, type: DocumentType) => { - let ctor = Mapping.get(type); + let ctor = ConstructorMap.get(type); if (!ctor) { return false; } @@ -82,28 +82,6 @@ export default class DictationManager { }); } - private sanitize = (title: string) => { - return title.replace("...", "").toLowerCase().trim(); - } - - public registerStatic = (keys: Array, action: IndependentAction, filter?: ActionPredicate) => { - let success = true; - keys.forEach(key => { - key = this.sanitize(key); - let existing = RegisteredCommands.Independent.get(key); - if (!existing) { - let unit = { - action: action, - filter: filter - }; - RegisteredCommands.Independent.set(key, unit); - } else { - success = false; - } - }); - return success; - } - public interpretNumber = (number: string) => { let initial = parseInt(number); if (!isNaN(initial)) { @@ -116,34 +94,38 @@ export default class DictationManager { return typeof converted === "string" ? parseInt(converted) : converted; } - public registerDynamic = (dynamicKey: RegExp, action: DependentAction) => { - RegisteredCommands.Dependent.push({ - expression: dynamicKey, - action: action - }); - } - @undoBatch public execute = async (phrase: string) => { - let target = SelectionManager.SelectedDocuments()[0]; - if (!target) { + let targets = SelectionManager.SelectedDocuments(); + if (!targets || !targets.length) { return; } - phrase = this.sanitize(phrase); let entry = RegisteredCommands.Independent.get(phrase); - if (entry && (!entry.restrictTo || validate(target, entry.restrictTo))) { - await entry.action(target); - return true; + if (entry) { + let success = false; + for (let target of targets) { + if (!entry.restrictTo || validate(target, entry.restrictTo)) { + await entry.action(target); + success = true; + } + } + return success; } for (let entry of RegisteredCommands.Dependent) { let regex = entry.expression; let matches = regex.exec(phrase); regex.lastIndex = 0; - if (matches !== null && (!entry.restrictTo || validate(target, entry.restrictTo))) { - await entry.action(target, matches); - return true; + if (matches !== null) { + let success = false; + for (let target of targets) { + if (!entry.restrictTo || validate(target, entry.restrictTo)) { + await entry.action(target, matches); + success = true; + } + } + return success; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 59c77f1c9..fa49e7175 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -123,14 +123,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); } - constructor(props: SubCollectionViewProps) { - super(props); - let fixed = DictationManager.Instance.registerStatic; - fixed(["Unset Fit To Container", "Set Fit To Container"], this.fitToContainer); - fixed(["Arrange contents in grid"], this.arrangeContents); - fixed(["Analyze Strokes"], this.analyzeStrokes); - } - @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 39574db0f..51661d1ae 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,7 +41,6 @@ import { ClientUtils } from '../../util/ClientUtils'; import { EditableView } from '../EditableView'; import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons'; import { DocumentDecorations } from '../DocumentDecorations'; -import { CognitiveServices } from '../../cognitive_services/CognitiveServices'; import DictationManager from '../../util/DictationManager'; import { CollectionViewType } from '../collections/CollectionBaseView'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 995ddd5c3..3e8582d61 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -384,7 +384,7 @@ export class FilterBox extends React.Component {
Collection Filters Active
: undefined} - ) + ); } // Useful queries: diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 0390359b3..6fbc92007 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -134,7 +134,7 @@ export class LinkContextMenu extends React.Component { - ) + ); } } diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx index a30104089..ed5ecd3ba 100644 --- a/src/client/views/search/ToggleBar.tsx +++ b/src/client/views/search/ToggleBar.tsx @@ -59,7 +59,7 @@ export class ToggleBar extends React.Component{ this._forwardTimeline.play(); this._forwardTimeline.reverse(); this.props.handleChange(); - console.log(this.props.getStatus()) + console.log(this.props.getStatus()); } @action.bound -- cgit v1.2.3-70-g09d2 From 982961c71fdd9d5dcd02ea33a2b631076a6a1f4b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 13:31:12 -0400 Subject: refactor --- src/client/util/DictationManager.ts | 307 +++++++++++---------- src/client/views/GlobalKeyHandler.ts | 14 +- .../views/collections/CollectionBaseView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 +- 4 files changed, 165 insertions(+), 162 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b0866a826..b51d309a1 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -11,183 +11,190 @@ import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; -namespace CORE { - export interface IWindow extends Window { - webkitSpeechRecognition: any; - } -} - -const ConstructorMap = new Map([ - [DocumentType.COL, listSpec(Doc)], - [DocumentType.AUDIO, AudioField], - [DocumentType.IMG, ImageField], - [DocumentType.HIST, HistogramField], - [DocumentType.IMPORT, listSpec(Doc)] -]); - -const tryCast = (view: DocumentView, type: DocumentType) => { - let ctor = ConstructorMap.get(type); - if (!ctor) { - return false; - } - return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined; -}; +export namespace DictationManager { -const validate = (target: DocumentView, types: DocumentType[]) => { - for (let type of types) { - if (tryCast(target, type)) { - return true; + namespace CORE { + export interface IWindow extends Window { + webkitSpeechRecognition: any; } } - return false; -}; - -const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; -export type IndependentAction = (target: DocumentView) => any | Promise; -export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; -export type RegistrationEntry = { action: IndependentAction, restrictTo?: DocumentType[] }; -export type ActionPredicate = (target: DocumentView) => boolean; -export type RegexEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] }; - -export default class DictationManager { - public static Instance = new DictationManager(); - private recognizer: any; - private isListening = false; - - constructor() { - this.recognizer = new webkitSpeechRecognition(); - this.recognizer.interimResults = false; - this.recognizer.continuous = true; - } - finish = (handler: any, data: any) => { - handler(data); - this.stop(); - } + const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; - stop = () => { - this.recognizer.stop(); - this.isListening = false; - } + let isListening = false; + const recognizer = (() => { + let initialized = new webkitSpeechRecognition(); + initialized.interimResults = false; + initialized.continuous = true; + return initialized; + })(); - listen = () => { - if (this.isListening) { - return undefined; - } - this.isListening = true; - this.recognizer.start(); - return new Promise((resolve, reject) => { - this.recognizer.onresult = (e: any) => this.finish(resolve, e.results[0][0].transcript); - this.recognizer.onerror = (e: any) => this.finish(reject, e); - }); - } + export namespace Controls { + + export const listen = () => { + if (isListening) { + return undefined; + } + isListening = true; + recognizer.start(); + return new Promise((resolve, reject) => { + recognizer.onresult = (e: any) => { + resolve(e.results[0][0].transcript); + stop(); + }; + recognizer.onerror = (e: any) => { + reject(e); + stop(); + }; + }); + }; + + export const stop = () => { + recognizer.stop(); + isListening = false; + }; - public interpretNumber = (number: string) => { - let initial = parseInt(number); - if (!isNaN(initial)) { - return initial; - } - let converted = converter.wordsToNumbers(number, { fuzzy: true }); - if (converted === null) { - return NaN; - } - return typeof converted === "string" ? parseInt(converted) : converted; } - @undoBatch - public execute = async (phrase: string) => { - let targets = SelectionManager.SelectedDocuments(); - if (!targets || !targets.length) { - return; - } + export namespace Commands { - let entry = RegisteredCommands.Independent.get(phrase); - if (entry) { - let success = false; - for (let target of targets) { - if (!entry.restrictTo || validate(target, entry.restrictTo)) { - await entry.action(target); - success = true; - } + export type IndependentAction = (target: DocumentView) => any | Promise; + export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] }; + + export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; + export type DependentEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] }; + + export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value); + export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry); + + export const execute = async (phrase: string) => { + let targets = SelectionManager.SelectedDocuments(); + if (!targets || !targets.length) { + return; } - return success; - } - for (let entry of RegisteredCommands.Dependent) { - let regex = entry.expression; - let matches = regex.exec(phrase); - regex.lastIndex = 0; - if (matches !== null) { + let entry = Independent.get(phrase); + if (entry) { let success = false; + let restrictTo = entry.restrictTo; for (let target of targets) { - if (!entry.restrictTo || validate(target, entry.restrictTo)) { - await entry.action(target, matches); + if (!restrictTo || validate(target, restrictTo)) { + await entry.action(target); success = true; } } return success; } - } - return false; - } + for (let entry of Dependent) { + let regex = entry.expression; + let matches = regex.exec(phrase); + regex.lastIndex = 0; + if (matches !== null) { + let success = false; + let restrictTo = entry.restrictTo; + for (let target of targets) { + if (!restrictTo || validate(target, restrictTo)) { + await entry.action(target, matches); + success = true; + } + } + return success; + } + } -} + return false; + }; + + const ConstructorMap = new Map([ + [DocumentType.COL, listSpec(Doc)], + [DocumentType.AUDIO, AudioField], + [DocumentType.IMG, ImageField], + [DocumentType.HIST, HistogramField], + [DocumentType.IMPORT, listSpec(Doc)] + ]); + + const tryCast = (view: DocumentView, type: DocumentType) => { + let ctor = ConstructorMap.get(type); + if (!ctor) { + return false; + } + return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined; + }; -export namespace RegisteredCommands { + const validate = (target: DocumentView, types: DocumentType[]) => { + for (let type of types) { + if (tryCast(target, type)) { + return true; + } + } + return false; + }; - export const Independent = new Map([ + const interpretNumber = (number: string) => { + let initial = parseInt(number); + if (!isNaN(initial)) { + return initial; + } + let converted = converter.wordsToNumbers(number, { fuzzy: true }); + if (converted === null) { + return NaN; + } + return typeof converted === "string" ? parseInt(converted) : converted; + }; - ["clear", { - action: (target: DocumentView) => { - Doc.GetProto(target.props.Document).data = new List(); - }, - restrictTo: [DocumentType.COL] - }], + const Independent = new Map([ - ["open fields", { - action: (target: DocumentView) => { - let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); - target.props.addDocTab(kvp, target.dataDoc, "onRight"); - } - }] - - ]); - - export const Dependent = new Array( - - { - expression: /create (\w+) documents of type (image|nested collection)/g, - action: (target: DocumentView, matches: RegExpExecArray) => { - let count = DictationManager.Instance.interpretNumber(matches[1]); - let what = matches[2]; - let dataDoc = Doc.GetProto(target.props.Document); - let fieldKey = "data"; - for (let i = 0; i < count; i++) { - let created: Doc | undefined; - switch (what) { - case "image": - created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); - break; - case "nested collection": - created = Docs.Create.FreeformDocument([], {}); - break; - } - created && Doc.AddDocToList(dataDoc, fieldKey, created); + ["clear", { + action: (target: DocumentView) => Doc.GetProto(target.props.Document).data = new List(), + restrictTo: [DocumentType.COL] + }], + + ["open fields", { + action: (target: DocumentView) => { + let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); + target.props.addDocTab(kvp, target.dataDoc, "onRight"); } + }] + + ]); + + const Dependent = new Array( + + { + expression: /create (\w+) documents of type (image|nested collection)/g, + action: (target: DocumentView, matches: RegExpExecArray) => { + let count = interpretNumber(matches[1]); + let what = matches[2]; + let dataDoc = Doc.GetProto(target.props.Document); + let fieldKey = "data"; + for (let i = 0; i < count; i++) { + let created: Doc | undefined; + switch (what) { + case "image": + created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); + break; + case "nested collection": + created = Docs.Create.FreeformDocument([], {}); + break; + } + created && Doc.AddDocToList(dataDoc, fieldKey, created); + } + }, + restrictTo: [DocumentType.COL] }, - restrictTo: [DocumentType.COL] - }, - - { - expression: /view as (freeform|stacking|masonry|schema|tree)/g, - action: (target: DocumentView, matches: RegExpExecArray) => { - let mode = CollectionViewType.ValueOf(matches[1]); - mode && (target.props.Document.viewType = mode); - }, - restrictTo: [DocumentType.COL] - } - ); + { + expression: /view as (freeform|stacking|masonry|schema|tree)/g, + action: (target: DocumentView, matches: RegExpExecArray) => { + let mode = CollectionViewType.valueOf(matches[1]); + mode && (target.props.Document.viewType = mode); + }, + restrictTo: [DocumentType.COL] + } + + ); + + } } \ No newline at end of file diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 99345f04e..609136bb5 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -5,10 +5,7 @@ import { MainView } from "./MainView"; import { DragManager } from "../util/DragManager"; import { action, runInAction } from "mobx"; import { Doc } from "../../new_fields/Doc"; -import { CognitiveServices } from "../cognitive_services/CognitiveServices"; -import DictationManager from "../util/DictationManager"; -import { ContextMenu } from "./ContextMenu"; -import { ContextMenuProps } from "./ContextMenuItem"; +import { DictationManager } from "../util/DictationManager"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; @@ -74,7 +71,7 @@ export default class KeyManager { } main.toggleColorPicker(true); SelectionManager.DeselectAll(); - DictationManager.Instance.stop(); + DictationManager.Controls.stop(); main.dictationOverlayVisible = false; main.dictationSuccess = undefined; main.overlayTimeout && clearTimeout(main.overlayTimeout); @@ -114,19 +111,18 @@ export default class KeyManager { let main = MainView.Instance; main.dictationOverlayVisible = true; main.isListening = true; - let dictation = DictationManager.Instance; - let command = await dictation.listen(); + let command = await DictationManager.Controls.listen(); main.isListening = false; if (!command) { break; } command = command.toLowerCase(); main.dictatedPhrase = command; - main.dictationSuccess = await dictation.execute(command); + main.dictationSuccess = await DictationManager.Commands.execute(command); main.overlayTimeout = setTimeout(() => { main.dictationOverlayVisible = false; main.dictationSuccess = undefined; - }, 3000); + }, 2000); stopPropagation = true; preventDefault = true; } diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 3f88ed98c..24604c812 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -34,7 +34,7 @@ export namespace CollectionViewType { ["masonry", CollectionViewType.Masonry] ]); - export const ValueOf = (value: string) => { + export const valueOf = (value: string) => { return stringMapping.get(value.toLowerCase()); }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 51661d1ae..c5d526a5a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,7 +41,7 @@ import { ClientUtils } from '../../util/ClientUtils'; import { EditableView } from '../EditableView'; import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons'; import { DocumentDecorations } from '../DocumentDecorations'; -import DictationManager from '../../util/DictationManager'; +import { DictationManager } from '../../util/DictationManager'; import { CollectionViewType } from '../collections/CollectionBaseView'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -539,7 +539,7 @@ export class DocumentView extends DocComponent(Docu } listen = async () => { - let transcript = await DictationManager.Instance.listen(); + let transcript = await DictationManager.Controls.listen(); transcript && (Doc.GetProto(this.props.Document).transcript = transcript); } -- cgit v1.2.3-70-g09d2 From d6fda11588f1a117e8acc30ea5600d34ff22e01b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sat, 3 Aug 2019 21:29:56 -0400 Subject: now differentiate between continuous, indefinite and, separately, interim vs final only dictation results --- src/client/util/DictationManager.ts | 81 +++++++++++++++++++++------------ src/client/views/GlobalKeyHandler.ts | 3 +- src/client/views/MainView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 3 +- src/new_fields/Doc.ts | 2 +- 5 files changed, 59 insertions(+), 32 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b02a5ecbe..89797f101 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -39,57 +39,82 @@ export namespace DictationManager { const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; let isListening = false; - const recognizer = (() => { - let initialized = new webkitSpeechRecognition(); - initialized.continuous = true; - initialized.language = "en-US"; - return initialized; - })(); + let isManuallyStopped = false; + const recognizer: SpeechRecognition = new webkitSpeechRecognition(); export namespace Controls { + let newestResult: string; export type InterimResultHandler = (results: any) => any; + export type ContinuityArgs = { indefinite: boolean } | false; + export interface ListeningOptions { + language: string; + continuous: ContinuityArgs; + interimHandler: InterimResultHandler; + delimiter: string; + } - export const listen = (handler: Opt = undefined) => { + export const listen = (options?: Partial) => { if (isListening) { return undefined; } isListening = true; + let handler = options ? options.interimHandler : undefined; + let continuous = options ? options.continuous : undefined; + let language = options ? options.language : undefined; + let delimiter = options ? options.delimiter : undefined; + recognizer.interimResults = handler !== undefined; + recognizer.continuous = continuous === undefined ? false : continuous !== false; + recognizer.lang = language === undefined ? "en-US" : language; + recognizer.start(); return new Promise((resolve, reject) => { + recognizer.onerror = (e: any) => { reject(e); stop(); }; - if (handler) { - let newestResult: string; - recognizer.onresult = (e: any) => { - newestResult = e.results[0][0].transcript; - handler(newestResult); - }; - recognizer.onend = (e: any) => { + + recognizer.onresult = (e: SpeechRecognitionEvent) => { + newestResult = synthesize(e, delimiter); + handler && handler(newestResult); + }; + + recognizer.onend = (e: Event) => { + if (continuous && continuous.indefinite && !isManuallyStopped) { + recognizer.start(); + } else { resolve(newestResult); - stop(); - }; - } else { - recognizer.onresult = (e: any) => { - let finalResult = e.results[0][0].transcript; - resolve(finalResult); - stop(); - }; - } + reset(); + } + }; + }); }; - export const stop = () => { - recognizer.stop(); + export const stop = (saveCumulative = true) => { + saveCumulative ? recognizer.stop() : recognizer.abort(); + reset(); + }; + + const reset = () => { isListening = false; - recognizer.onresult = undefined; - recognizer.onend = undefined; - recognizer.onerror = undefined; + isManuallyStopped = false; + recognizer.onresult = null; + recognizer.onend = null; + recognizer.onerror = null; + }; + + const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => { + let results = e.results; + let transcripts: string[] = []; + for (let i = 0; i < results.length; i++) { + transcripts.push(results.item(i).item(0).transcript.trim()); + } + return transcripts.join(delimiter || "..."); }; } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 82289c249..98df43a1e 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -112,7 +112,8 @@ export default class KeyManager { main.dictationOverlayVisible = true; main.isListening = true; - let command = await DictationManager.Controls.listen((results: any) => console.log(results)); + // let printer = (results: any) => console.log(results); + let command = await DictationManager.Controls.listen(); main.isListening = false; if (!command) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 748e1e634..5cec34293 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -49,7 +49,7 @@ export class MainView extends React.Component { @observable public pwidth: number = 0; @observable public pheight: number = 0; - @observable private dictationState = "Listening..."; + @observable private dictationState = ""; @observable private dictationSuccessState: boolean | undefined = undefined; @observable private dictationDisplayState = false; @observable private dictationListeningState = false; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c5d526a5a..bd87bf21b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -539,7 +539,8 @@ export class DocumentView extends DocComponent(Docu } listen = async () => { - let transcript = await DictationManager.Controls.listen(); + let options = { continuous: { indefinite: true }, delimiter: " " }; + let transcript = await DictationManager.Controls.listen(options); transcript && (Doc.GetProto(this.props.Document).transcript = transcript); } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 84b8589dd..979574f16 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -531,7 +531,7 @@ export namespace Doc { d.layout = detailLayout; d.nativeWidth = d.nativeHeight = undefined; if (detailLayout instanceof Doc) { - let delegDetailLayout = Doc.MakeDelegate(detailLayout) as Doc; + let delegDetailLayout = Doc.MakeDelegate(detailLayout); d.layout = delegDetailLayout; let subDetailLayout1 = await PromiseValue(delegDetailLayout.detailedLayout); let subDetailLayout = await PromiseValue(delegDetailLayout.detailedLayout); -- cgit v1.2.3-70-g09d2 From 6d718c8a243e68d23199d35592bfded285385c91 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 05:09:56 -0400 Subject: now all listen() calls display recording UI --- src/client/util/DictationManager.ts | 24 ++++++++++++++++++++---- src/client/views/GlobalKeyHandler.ts | 21 ++++----------------- src/client/views/Main.scss | 1 - src/client/views/MainView.tsx | 11 ++++++++--- src/client/views/nodes/DocumentView.tsx | 16 +++++++++++++--- 5 files changed, 45 insertions(+), 28 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index b6f871713..a882994c1 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -10,6 +10,7 @@ import { Cast, CastCtor } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; +import { MainView } from "../views/MainView"; /** * This namespace provides a singleton instance of a manager that @@ -37,9 +38,11 @@ export namespace DictationManager { } } const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; + export const placeholder = "Listening..."; export namespace Controls { + const infringe = "unable to process: dictation manager still involved in previous session"; const intraSession = ". "; const interSession = " ... "; @@ -52,7 +55,7 @@ export namespace DictationManager { const recognizer: SpeechRecognition = new webkitSpeechRecognition() || new SpeechRecognition(); recognizer.onstart = () => console.log("initiating speech recognition session..."); - export type InterimResultHandler = (results: any) => any; + export type InterimResultHandler = (results: string) => any; export type ContinuityArgs = { indefinite: boolean } | false; export type DelimiterArgs = { inter: string, intra: string }; @@ -61,21 +64,33 @@ export namespace DictationManager { continuous: ContinuityArgs; delimiters: DelimiterArgs; interimHandler: InterimResultHandler; + tryExecute: boolean; } export const listen = async (options?: Partial) => { - let results: any; + let results: string | undefined; + MainView.Instance.dictationOverlayVisible = true; + MainView.Instance.isListening = true; try { results = await listenImpl(options); + if (results) { + MainView.Instance.isListening = false; + MainView.Instance.dictatedPhrase = results = results.toLowerCase(); + MainView.Instance.dictationSuccess = options && options.tryExecute ? await DictationManager.Commands.execute(results) : true; + } } catch (e) { - results = `Dictation Error: ${"error" in e ? e.error : "unknown error"}`; + MainView.Instance.isListening = false; + MainView.Instance.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; + MainView.Instance.dictationSuccess = false; + } finally { + MainView.Instance.initiateDictationFade(); } return results; }; const listenImpl = (options?: Partial) => { if (isListening) { - return undefined; + return infringe; } isListening = true; @@ -126,6 +141,7 @@ export namespace DictationManager { } else { resolve(current); } + current = undefined; reset(); }; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index c3e6ae6c8..0989e8db1 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -73,9 +73,11 @@ export default class KeyManager { SelectionManager.DeselectAll(); DictationManager.Controls.stop(); if (main.dictationOverlayVisible) { + main.cancelDictationFade(); main.dictationOverlayVisible = false; + main.isListening = true; + main.dictatedPhrase = ""; main.dictationSuccess = undefined; - main.cancelDictationFade(); } break; case "delete": @@ -110,22 +112,7 @@ export default class KeyManager { switch (keyname) { case " ": - let main = MainView.Instance; - main.dictationOverlayVisible = true; - - main.isListening = true; - // let printer = (results: any) => console.log(results); - let command = await DictationManager.Controls.listen(); - main.isListening = false; - - if (!command) { - break; - } - - main.dictatedPhrase = command = command.toLowerCase(); - main.dictationSuccess = await DictationManager.Commands.execute(command); - main.initiateDictationFade(); - + DictationManager.Controls.listen({ tryExecute: true }); stopPropagation = true; preventDefault = true; } diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 8e57b88c3..f76abaff3 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -292,7 +292,6 @@ ul#add-options-list { width: 100%; height: 100%; position: absolute; - background: darkslategray; z-index: 999; transition: 0.5s all ease; pointer-events: none; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4443eea6d..383efa1e3 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,7 +1,7 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faPlay, faPause, faCaretUp, faLongArrowAltRight, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat, faBolt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx'; +import { action, computed, configure, observable, runInAction, reaction, trace, autorun } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; @@ -167,6 +167,8 @@ export class MainView extends React.Component { } } + autorun(() => console.log(`this.isListening = ${this.isListening}`)); + library.add(faFont); library.add(faExclamation); library.add(faPortrait); @@ -523,7 +525,7 @@ export class MainView extends React.Component { render() { let display = this.dictationOverlayVisible; let success = this.dictationSuccess; - let result = this.isListening ? "Listening..." : `"${this.dictatedPhrase}"`; + let result = this.isListening ? DictationManager.placeholder : `"${this.dictatedPhrase}"`; return (
{result}
{this.mainContent} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1d9cb3c80..a415aefda 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -38,6 +38,7 @@ import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); import { DictationManager } from '../../util/DictationManager'; +import { MainView } from '../MainView'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -536,9 +537,18 @@ export class DocumentView extends DocComponent(Docu } listen = async () => { - let options = { continuous: { indefinite: true }, delimiter: " " }; - let transcript = await DictationManager.Controls.listen(options); - transcript && (Doc.GetProto(this.props.Document).transcript = transcript); + let dataDoc = Doc.GetProto(this.props.Document); + let options = { + continuous: { indefinite: true }, + delimiter: " ", + interimHandler: (results: string) => { + MainView.Instance.isListening = false; + MainView.Instance.dictationSuccess = true; + MainView.Instance.dictatedPhrase = results; + } + }; + let final = await DictationManager.Controls.listen(options); + final && (dataDoc.transcript = final); } @action -- cgit v1.2.3-70-g09d2 From 1ed381022450bc5c39238c73f732179f4b21daf2 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 05:13:51 -0400 Subject: clean up --- src/client/views/nodes/DocumentView.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a415aefda..458fb582d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -538,16 +538,15 @@ export class DocumentView extends DocComponent(Docu listen = async () => { let dataDoc = Doc.GetProto(this.props.Document); - let options = { - continuous: { indefinite: true }, - delimiter: " ", - interimHandler: (results: string) => { - MainView.Instance.isListening = false; - MainView.Instance.dictationSuccess = true; - MainView.Instance.dictatedPhrase = results; - } + let handler = (results: string) => { + MainView.Instance.isListening = false; + MainView.Instance.dictationSuccess = true; + MainView.Instance.dictatedPhrase = results; }; - let final = await DictationManager.Controls.listen(options); + let final = await DictationManager.Controls.listen({ + continuous: { indefinite: true }, + interimHandler: handler + }); final && (dataDoc.transcript = final); } -- cgit v1.2.3-70-g09d2 From 0010f88e1002feb14ecfb111c2c6ae56ee34cf2d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 13:32:19 -0400 Subject: clean up and UI fix --- src/client/util/DictationManager.ts | 43 +++++++++++++++++++-------------- src/client/views/MainView.tsx | 11 ++++----- src/client/views/nodes/DocumentView.tsx | 20 ++++++--------- 3 files changed, 38 insertions(+), 36 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 2af7c53cb..ee1f11b4f 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -58,6 +58,7 @@ export namespace DictationManager { export type InterimResultHandler = (results: string) => any; export type ContinuityArgs = { indefinite: boolean } | false; export type DelimiterArgs = { inter: string, intra: string }; + export type ListeningUIStatus = { interim: boolean } | false; export interface ListeningOptions { language: string; @@ -69,22 +70,28 @@ export namespace DictationManager { export const listen = async (options?: Partial) => { let results: string | undefined; - MainView.Instance.dictationOverlayVisible = true; - MainView.Instance.isListening = true; + let main = MainView.Instance; + + main.dictationOverlayVisible = true; + let interim = options !== undefined && options.interimHandler !== undefined; + main.isListening = { interim: interim }; + try { results = await listenImpl(options); if (results) { - MainView.Instance.isListening = false; - MainView.Instance.dictatedPhrase = results = results.toLowerCase(); - MainView.Instance.dictationSuccess = options && options.tryExecute ? await DictationManager.Commands.execute(results) : true; + main.isListening = false; + main.dictatedPhrase = results; + let execute = options && options.tryExecute; + main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; } } catch (e) { - MainView.Instance.isListening = false; - MainView.Instance.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; - MainView.Instance.dictationSuccess = false; + main.isListening = false; + main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`; + main.dictationSuccess = false; } finally { - MainView.Instance.initiateDictationFade(); + main.initiateDictationFade(); } + return results; }; @@ -137,11 +144,10 @@ export namespace DictationManager { let complete = () => { if (indefinite) { current && sessionResults.push(current); - resolve(sessionResults.join(inter || interSession)); + sessionResults.length && resolve(sessionResults.join(inter || interSession)); } else { resolve(current); } - current = undefined; reset(); }; @@ -154,12 +160,12 @@ export namespace DictationManager { } isManuallyStopped = true; salvageSession ? recognizer.stop() : recognizer.abort(); - if (MainView.Instance.dictationOverlayVisible) { - MainView.Instance.cancelDictationFade(); - MainView.Instance.dictationOverlayVisible = false; - MainView.Instance.isListening = true; - MainView.Instance.dictatedPhrase = ""; - MainView.Instance.dictationSuccess = undefined; + let main = MainView.Instance; + if (main.dictationOverlayVisible) { + main.cancelDictationFade(); + main.dictationOverlayVisible = false; + main.dictationSuccess = undefined; + setTimeout(() => main.dictatedPhrase = placeholder, 500); } }; @@ -173,12 +179,13 @@ export namespace DictationManager { }; const reset = () => { + current = undefined; + sessionResults = []; isListening = false; isManuallyStopped = false; recognizer.onresult = null; recognizer.onerror = null; recognizer.onend = null; - sessionResults = []; }; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 383efa1e3..631d24cb1 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -49,10 +49,10 @@ export class MainView extends React.Component { @observable public pwidth: number = 0; @observable public pheight: number = 0; - @observable private dictationState = ""; + @observable private dictationState = DictationManager.placeholder; @observable private dictationSuccessState: boolean | undefined = undefined; @observable private dictationDisplayState = false; - @observable private dictationListeningState = false; + @observable private dictationListeningState: DictationManager.Controls.ListeningUIStatus = false; public overlayTimeout: NodeJS.Timeout | undefined; @@ -61,6 +61,7 @@ export class MainView extends React.Component { this.overlayTimeout = setTimeout(() => { this.dictationOverlayVisible = false; this.dictationSuccess = undefined; + setTimeout(() => this.dictatedPhrase = DictationManager.placeholder, 500); }, duration); } @@ -116,7 +117,7 @@ export class MainView extends React.Component { return this.dictationListeningState; } - public set isListening(value: boolean) { + public set isListening(value: DictationManager.Controls.ListeningUIStatus) { runInAction(() => this.dictationListeningState = value); } @@ -167,8 +168,6 @@ export class MainView extends React.Component { } } - autorun(() => console.log(`this.isListening = ${this.isListening}`)); - library.add(faFont); library.add(faExclamation); library.add(faPortrait); @@ -525,7 +524,7 @@ export class MainView extends React.Component { render() { let display = this.dictationOverlayVisible; let success = this.dictationSuccess; - let result = this.isListening ? DictationManager.placeholder : `"${this.dictatedPhrase}"`; + let result = this.isListening && !this.isListening.interim ? DictationManager.placeholder : `"${this.dictatedPhrase}"`; return (
(Docu this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true; } - listen = async () => { - let dataDoc = Doc.GetProto(this.props.Document); - let handler = (results: string) => { - MainView.Instance.isListening = false; - MainView.Instance.dictationSuccess = true; - MainView.Instance.dictatedPhrase = results; - }; - let final = await DictationManager.Controls.listen({ + listen = async () => + Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({ continuous: { indefinite: true }, - interimHandler: handler - }); - final && (dataDoc.transcript = final); - } + interimHandler: (results: string) => { + MainView.Instance.isListening = { interim: true }; + MainView.Instance.dictationSuccess = true; + MainView.Instance.dictatedPhrase = results; + } + }) @action onContextMenu = async (e: React.MouseEvent): Promise => { -- cgit v1.2.3-70-g09d2 From 857e98b16f693db968b04ede62e3f864f8b2ed35 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 4 Aug 2019 14:04:22 -0400 Subject: final UI tweaks --- src/client/util/DictationManager.ts | 5 ++--- src/client/views/MainView.tsx | 8 ++++---- src/client/views/nodes/DocumentView.tsx | 12 +++++++----- 3 files changed, 13 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index ee1f11b4f..978889830 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -73,15 +73,14 @@ export namespace DictationManager { let main = MainView.Instance; main.dictationOverlayVisible = true; - let interim = options !== undefined && options.interimHandler !== undefined; - main.isListening = { interim: interim }; + main.isListening = { interim: false }; try { results = await listenImpl(options); if (results) { main.isListening = false; - main.dictatedPhrase = results; let execute = options && options.tryExecute; + main.dictatedPhrase = execute ? results.toLowerCase() : results; main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; } } catch (e) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 631d24cb1..618e83bfa 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -89,7 +89,7 @@ export class MainView extends React.Component { } } - public get dictatedPhrase() { + @computed public get dictatedPhrase() { return this.dictationState; } @@ -97,7 +97,7 @@ export class MainView extends React.Component { runInAction(() => this.dictationState = value); } - public get dictationSuccess() { + @computed public get dictationSuccess() { return this.dictationSuccessState; } @@ -105,7 +105,7 @@ export class MainView extends React.Component { runInAction(() => this.dictationSuccessState = value); } - public get dictationOverlayVisible() { + @computed public get dictationOverlayVisible() { return this.dictationDisplayState; } @@ -113,7 +113,7 @@ export class MainView extends React.Component { runInAction(() => this.dictationDisplayState = value); } - public get isListening() { + @computed public get isListening() { return this.dictationListeningState; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d6ae17537..57e66ff1b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -536,15 +536,17 @@ export class DocumentView extends DocComponent(Docu this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true; } - listen = async () => + listen = async () => { Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({ continuous: { indefinite: true }, interimHandler: (results: string) => { - MainView.Instance.isListening = { interim: true }; - MainView.Instance.dictationSuccess = true; - MainView.Instance.dictatedPhrase = results; + let main = MainView.Instance; + main.dictationSuccess = true; + main.dictatedPhrase = results; + main.isListening = { interim: true }; } - }) + }); + } @action onContextMenu = async (e: React.MouseEvent): Promise => { -- cgit v1.2.3-70-g09d2 From 2978e805156bffad92e8ac23259843a7cf207fb7 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 6 Aug 2019 11:11:26 -0400 Subject: beginning link to doc in context --- src/client/util/DictationManager.ts | 4 ++-- src/client/views/nodes/DocumentView.tsx | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 3668ca0d2..9c61fe125 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -11,7 +11,7 @@ import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { MainView } from "../views/MainView"; -import { Clipboard } from "ts-clipboard"; +import { Utils } from "../../Utils"; /** * This namespace provides a singleton instance of a manager that @@ -79,7 +79,7 @@ export namespace DictationManager { try { results = await listenImpl(options); if (results) { - Clipboard.copy(results); + Utils.CopyText(results); main.isListening = false; let execute = options && options.tryExecute; main.dictatedPhrase = execute ? results.toLowerCase() : results; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 57e66ff1b..e5b0b0b52 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -602,6 +602,14 @@ export class DocumentView extends DocComponent(Docu if (!ClientUtils.RELEASE) { let copies: ContextMenuProps[] = []; copies.push({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" }); + copies.push({ + description: "Copy Context", event: () => { + let parent = this.props.ContainingCollectionView; + if (parent) { + Utils.CopyText(Utils.prepend("/doc/" + parent.props.Document[Id])); + } + }, icon: "link" + }); copies.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" }); cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" }); } -- cgit v1.2.3-70-g09d2 From 572c4196e0f41ec6bae8cae403812f9b97d5a3c7 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 6 Aug 2019 13:27:47 -0400 Subject: post factored out, buxton pivot viewer route --- src/Utils.ts | 11 +++++++++++ src/client/views/nodes/DocumentView.tsx | 1 + src/server/index.ts | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/Utils.ts b/src/Utils.ts index 502540eb0..c1737a084 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -3,6 +3,7 @@ import v5 = require("uuid/v5"); import { Socket } from 'socket.io'; import { Message } from './server/Message'; import { RouteStore } from './server/RouteStore'; +import requestPromise = require('request-promise'); export class Utils { @@ -173,4 +174,14 @@ export namespace JSONUtils { return results; } +} + +export function PostToServer(relativeRoute: string, body: any) { + let options = { + method: "POST", + uri: Utils.prepend(relativeRoute), + json: true, + body: body + }; + return requestPromise.post(options); } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 547d858ce..dbbf95479 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -39,6 +39,7 @@ import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); import { DictationManager } from '../../util/DictationManager'; import { MainView } from '../MainView'; +import requestPromise = require('request-promise'); const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); diff --git a/src/server/index.ts b/src/server/index.ts index 10a84c823..1e811c1b2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -148,6 +148,25 @@ app.get("/pull", (req, res) => res.redirect("/"); })); +app.get("/buxton/:clear", (req, res) => { + if (req.params.clear === "true") { + deleteFields().then(() => upload_buxton_docs(res)); + } else { + upload_buxton_docs(res); + } +}); + +let upload_buxton_docs = (res: Response) => { + let buxton_scraping = path.join(__dirname, '../scraping/buxton'); + exec('python scraper.py', { cwd: buxton_scraping }, (err, stdout, sterr) => { + if (err) { + res.send(err.message); + return; + } + res.redirect("/"); + }); +}; + app.get("/version", (req, res) => { exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => { if (err) { -- cgit v1.2.3-70-g09d2 From ebddce0975fa7e224e022cc075eee71abeacbe1d Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 7 Aug 2019 23:27:21 -0400 Subject: changed pdfs to render regions as collections. changed libraryBrush implementation. --- src/client/views/SearchItem.tsx | 6 ++-- .../views/collections/CollectionDockingView.tsx | 2 +- .../views/collections/CollectionSchemaView.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 8 ++--- src/client/views/nodes/DocumentView.tsx | 37 ++++++++++----------- src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 3 +- src/client/views/pdf/Page.tsx | 9 ++--- .../views/presentationview/PresentationElement.tsx | 4 +-- src/client/views/search/SearchItem.tsx | 38 +++++++--------------- src/new_fields/Doc.ts | 18 ++++++++++ src/new_fields/util.ts | 1 - src/scraping/buxton/scraper.py | 9 ++--- 13 files changed, 65 insertions(+), 74 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx index 13e4b88f7..fd4b2420d 100644 --- a/src/client/views/SearchItem.tsx +++ b/src/client/views/SearchItem.tsx @@ -37,12 +37,10 @@ export class SearchItem extends React.Component { return ; } onPointerEnter = (e: React.PointerEvent) => { - this.props.doc.libraryBrush = true; - Doc.SetOnPrototype(this.props.doc, "protoBrush", true); + Doc.BrushDoc(this.props.doc); } onPointerLeave = (e: React.PointerEvent) => { - this.props.doc.libraryBrush = false; - Doc.SetOnPrototype(this.props.doc, "protoBrush", false); + Doc.UnBrushDoc(this.props.doc); } collectionRef = React.createRef(); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index f559480ed..3af92cbe3 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -405,7 +405,7 @@ export class CollectionDockingView extends React.Component, dragSpan); + }}>, dragSpan); ReactDOM.render( CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv); tab.reactComponents = [dragSpan, upDiv]; tab.element.append(dragSpan); diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 75787c0a8..ebfa737be 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -50,7 +50,7 @@ const columnTypes: Map = new Map([ ["title", ColumnType.String], ["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number], ["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean], - ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["libraryBrush", ColumnType.Boolean], ["zIndex", ColumnType.Number] + ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number] ]); @observer diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 571967743..24bd24d11 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -127,19 +127,19 @@ class TreeView extends React.Component { onPointerDown = (e: React.PointerEvent) => e.stopPropagation(); onPointerEnter = (e: React.PointerEvent): void => { - this.props.active() && (this.props.document.libraryBrush = true); + this.props.active() && Doc.BrushDoc(this.dataDoc); if (e.buttons === 1 && SelectionManager.GetIsDragging()) { this._header!.current!.className = "treeViewItem-header"; document.addEventListener("pointermove", this.onDragMove, true); } } onPointerLeave = (e: React.PointerEvent): void => { - this.props.document.libraryBrush = false; + Doc.UnBrushDoc(this.dataDoc); this._header!.current!.className = "treeViewItem-header"; document.removeEventListener("pointermove", this.onDragMove, true); } onDragMove = (e: PointerEvent): void => { - this.props.document.libraryBrush = false; + Doc.UnBrushDoc(this.dataDoc); let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); let rect = this._header!.current!.getBoundingClientRect(); let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); @@ -359,7 +359,7 @@ class TreeView extends React.Component { return <>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a7b4f33db..e911dc47c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -303,7 +303,7 @@ export class DocumentView extends DocComponent(Docu fullScreenAlias.showCaption = true; this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab"); SelectionManager.DeselectAll(); - this.props.Document.libraryBrush = false; + Doc.UnBrushDoc(this.props.Document); } else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && @@ -445,13 +445,11 @@ export class DocumentView extends DocComponent(Docu targetDoc.targetContext = de.data.targetContext; let annotations = await DocListCastAsync(annotationDoc.annotations); if (annotations) { - annotations.forEach(anno => { - anno.target = targetDoc; - }); + annotations.forEach(anno => anno.target = targetDoc); } - let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc); - if (pdfDoc) { - DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title)); + let annotDoc = await Cast(annotationDoc.annotationOn, Doc); + if (annotDoc) { + DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(annotDoc.title)}`, "", StrCast(annotDoc.title)); } } if (de.data instanceof DragManager.LinkDragData) { @@ -647,8 +645,8 @@ export class DocumentView extends DocComponent(Docu }); } - onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; }; - onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }; + onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); }; + onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.props.Document); }; isSelected = () => SelectionManager.IsSelected(this); @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; @@ -695,22 +693,23 @@ export class DocumentView extends DocComponent(Docu }); } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith(" { annoDoc.color = color; annoDoc.type = AnnotationTypes.Region; annoDocs.push(annoDoc); - annoDoc.isBackground = true; + annoDoc.isButton = true; anno.remove(); this.props.addDocument && this.props.addDocument(annoDoc, false); mainAnnoDoc = annoDoc; @@ -208,7 +208,6 @@ export class PDFViewer extends React.Component { mainAnnoDocProto.y = Math.max(minY, 0); mainAnnoDocProto.annotations = new List(annoDocs); } - mainAnnoDocProto.pdfDoc = this.props.Document; mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title); mainAnnoDocProto.annotationOn = this.props.Document; if (sourceDoc && createLink) { diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 4986f44d5..6bd98cbaa 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -112,8 +112,8 @@ export default class Page extends React.Component { if (!BoolCast(annotationDoc.linkedToDoc)) { let annotations = await DocListCastAsync(annotationDoc.annotations); annotations && annotations.forEach(anno => anno.target = targetDoc); - let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc); - pdfDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title)) + let parentDoc = await Cast(annotationDoc.annotationOn, Doc); + parentDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(parentDoc.title)}`, "", StrCast(parentDoc.title)) } } }, @@ -144,10 +144,7 @@ export default class Page extends React.Component { onPointerDown = (e: React.PointerEvent): void => { // if alt+left click, drag and annotate if (NumCast(this.props.Document.scale, 1) !== 1) return; - if (e.altKey && e.button === 0) { - e.stopPropagation(); - } - else if (e.button === 0) { + if (!e.altKey && e.button === 0) { PDFMenu.Instance.StartDrag = this.startDrag; PDFMenu.Instance.Highlight = this.highlight; PDFMenu.Instance.Snippet = this.createSnippet; diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index e2d8daea9..d98b66324 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -706,7 +706,7 @@ export default class PresentationElement extends React.Component { - this.props.document.libraryBrush = false; + Doc.UnBrushDoc(this.props.document); let x = this.ScreenToLocalListTransform(e.clientX, e.clientY); let rect = this.header!.getBoundingClientRect(); let bounds = this.ScreenToLocalListTransform(rect.left, rect.top + rect.height / 2); @@ -889,7 +889,7 @@ export default class PresentationElement extends React.Component { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}> diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index c4af30f5c..8201aa374 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -105,23 +105,11 @@ export interface LinkMenuProps { @observer export class LinkContextMenu extends React.Component { - highlightDoc = (doc: Doc) => { - return () => { - doc.libraryBrush = true; - }; - } + highlightDoc = (doc: Doc) => () => Doc.BrushDoc(doc); - unHighlightDoc = (doc: Doc) => { - return () => { - doc.libraryBrush = false; - }; - } + unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc); - getOnClick(col: Doc) { - return () => { - CollectionDockingView.Instance.AddRightSplit(col, undefined); - }; - } + getOnClick = (col: Doc) => () => CollectionDockingView.Instance.AddRightSplit(col, undefined); render() { return ( @@ -286,14 +274,12 @@ export class SearchItem extends React.Component { let doc1 = Cast(this.props.doc.anchor1, Doc, null); let doc2 = Cast(this.props.doc.anchor2, Doc, null); - doc1 && (doc1.libraryBrush = true); - doc2 && (doc2.libraryBrush = true); + Doc.BrushDoc(doc1); + Doc.BrushDoc(doc2); } } else { - let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc); - docViews.forEach(element => { - element.props.Document.libraryBrush = true; - }); + DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element => + Doc.BrushDoc(element.props.Document)); } } @@ -303,14 +289,12 @@ export class SearchItem extends React.Component { let doc1 = Cast(this.props.doc.anchor1, Doc, null); let doc2 = Cast(this.props.doc.anchor2, Doc, null); - doc1 && (doc1.libraryBrush = false); - doc2 && (doc2.libraryBrush = false); + Doc.UnBrushDoc(doc1); + Doc.UnBrushDoc(doc2); } } else { - let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc); - docViews.forEach(element => { - element.props.Document.libraryBrush = false; - }); + DocumentManager.Instance.getAllDocumentViews(this.props.doc). + forEach(element => Doc.UnBrushDoc(element.props.Document)); } } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index c01f4e8cf..b3d1dc109 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -542,4 +542,22 @@ export namespace Doc { } }); } + + export class DocBrush { + @observable BrushedDoc: Doc[] = []; + } + const manager = new DocBrush(); + export function IsBrushed(doc: Doc) { + return manager.BrushedDoc.some(d => Doc.AreProtosEqual(d, doc)); + } + export function IsBrushedDegree(doc: Doc) { + return manager.BrushedDoc.some(d => d === doc) ? 2 : Doc.IsBrushed(doc) ? 1 : 0; + } + export function BrushDoc(doc: Doc) { + if (manager.BrushedDoc.indexOf(doc) === -1) runInAction(() => manager.BrushedDoc.push(doc)); + } + export function UnBrushDoc(doc: Doc) { + let index = manager.BrushedDoc.indexOf(doc); + if (index !== -1) runInAction(() => manager.BrushedDoc.splice(index, 1)); + } } \ No newline at end of file diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index c6f693f7f..48ae9f216 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -6,7 +6,6 @@ import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; import { action } from "mobx"; import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; -import { ComputedField } from "./ScriptField"; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index f0f45d8f9..29cb8a256 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -88,8 +88,7 @@ def write_schema(parse_results, display_fields, storage_key): "panX": 0, "panY": 0, "zoomBasis": 1, - "zIndex": 2, - "libraryBrush": False, + "zIndex": 2 "viewType": 2 }, "__type": "Doc" @@ -130,8 +129,7 @@ def write_text_doc(content): "x": 10, "y": 10, "width": 400, - "zIndex": 2, - "libraryBrush": False + "zIndex": 2 }, "__type": "Doc" } @@ -183,8 +181,7 @@ def write_image(folder, name): "x": 10, "y": 10, "width": min(800, native_width), - "zIndex": 2, - "libraryBrush": False + "zIndex": 2 }, "__type": "Doc" } -- cgit v1.2.3-70-g09d2 From 316c241d72fb83aad5f2bf9b143c317fdc906654 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 8 Aug 2019 12:23:47 -0400 Subject: fixed issues with jumptoDocument --- package.json | 2 +- src/client/documents/Documents.ts | 3 +-- src/client/util/DocumentManager.ts | 12 ++++++----- .../views/collections/CollectionBaseView.tsx | 5 ++--- src/client/views/nodes/DocumentView.tsx | 24 +++++++++------------- src/client/views/nodes/LinkMenuItem.tsx | 4 ++-- src/client/views/pdf/PDFViewer.tsx | 2 +- src/client/views/pdf/Page.tsx | 3 +-- 8 files changed, 25 insertions(+), 30 deletions(-) (limited to 'src/client/views/nodes/DocumentView.tsx') diff --git a/package.json b/package.json index 29a903d71..d699d1e6f 100644 --- a/package.json +++ b/package.json @@ -218,4 +218,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a8545206a..7dd853156 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -596,7 +596,7 @@ export namespace Docs { export namespace DocUtils { - export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", tags: string = "Default", sourceContext?: Doc) { + export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc) { if (LinkManager.Instance.doesLinkExist(source, target)) return undefined; let sv = DocumentManager.Instance.getDocumentView(source); if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return; @@ -610,7 +610,6 @@ export namespace DocUtils { linkDocProto.sourceContext = sourceContext; linkDocProto.title = title === "" ? source.title + " to " + target.title : title; linkDocProto.linkDescription = description; - linkDocProto.linkTags = tags; linkDocProto.type = DocumentType.LINK; linkDocProto.anchor1 = source; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0d46df406..7f526b247 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,5 +1,5 @@ import { action, computed, observable } from 'mobx'; -import { Doc } from '../../new_fields/Doc'; +import { Doc, DocListCastAsync } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; import { Cast, NumCast } from '../../new_fields/Types'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; @@ -140,13 +140,15 @@ export class DocumentManager { if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) { Doc.BrushDoc(docView.props.Document); if (linkPage !== undefined) docView.props.Document.curPage = linkPage; - UndoManager.RunInBatch(() => { - docView!.props.focus(docView!.props.Document, willZoom); - }, "focus"); + UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus"); } else { if (!contextDoc) { - if (docContext) { + let docs = docContext ? await DocListCastAsync(docContext.data) : undefined; + let found = false; + docs && docs.map(d => found = found || Doc.AreProtosEqual(d, docDelegate)); + if (docContext && found) { let targetContextView: DocumentView | null; + if (!forceDockFunc && docContext && (targetContextView = DocumentManager.Instance.getDocumentView(docContext))) { docContext.panTransformType = "Ease"; targetContextView.props.focus(docDelegate, willZoom); diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index b53e83eb1..cad87ebcc 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -99,7 +99,7 @@ export class CollectionBaseView extends React.Component { addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { var curPage = NumCast(this.props.Document.curPage, -1); Doc.GetProto(doc).page = curPage; - if (curPage >= 0) { + if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } allowDuplicates = true; @@ -126,8 +126,7 @@ export class CollectionBaseView extends React.Component { let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1); PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => - annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined) - ); + annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)); if (index !== -1) { value.splice(index, 1); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 861b53abf..c8eab85c2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -8,7 +8,7 @@ import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { List } from "../../../new_fields/List"; import { ObjectField } from "../../../new_fields/ObjectField"; import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { RouteStore } from '../../../server/RouteStore'; import { emptyFunction, returnTrue, Utils } from "../../../Utils"; @@ -359,12 +359,12 @@ export class DocumentView extends DocComponent(Docu if (!linkedFwdDocs.some(l => l instanceof Promise)) { let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab"); let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; - DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => { - this.props.focus(this.props.Document, true, 1); - setTimeout(() => - this.props.addDocTab(document, undefined, maxLocation), 1000); - } - , linkedFwdPage[altKey ? 1 : 0], targetContext); + DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, + document => { // open up target if it's not already in view ... + this.props.focus(this.props.Document, true, 1); // by zooming into the button document first + setTimeout(() => this.props.addDocTab(document, undefined, maxLocation), 1000); // then after the 1sec animation, open up the target in a new tab + }, + linkedFwdPage[altKey ? 1 : 0], targetContext); } } } @@ -445,13 +445,9 @@ export class DocumentView extends DocComponent(Docu let targetDoc = this.props.Document; targetDoc.targetContext = de.data.targetContext; let annotations = await DocListCastAsync(annotationDoc.annotations); - if (annotations) { - annotations.forEach(anno => anno.target = targetDoc); - } - let annotDoc = await Cast(annotationDoc.annotationOn, Doc); - if (annotDoc) { - DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(annotDoc.title)}`, "", StrCast(annotDoc.title)); - } + annotations && annotations.forEach(anno => anno.target = targetDoc); + + DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`); } if (de.data instanceof DragManager.LinkDragData) { let sourceDoc = de.data.linkSourceDocument; diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx index 1d4fcad69..a119eb39b 100644 --- a/src/client/views/nodes/LinkMenuItem.tsx +++ b/src/client/views/nodes/LinkMenuItem.tsx @@ -6,7 +6,7 @@ import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; import './LinkMenu.scss'; import React = require("react"); -import { Doc } from '../../../new_fields/Doc'; +import { Doc, DocListCastAsync } from '../../../new_fields/Doc'; import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types'; import { observable, action } from 'mobx'; import { LinkManager } from '../../util/LinkManager'; @@ -52,7 +52,7 @@ export class LinkMenuItem extends React.Component { } if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(targetContext!)); + DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!); } else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!)); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 7cd62f4e0..08674720d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -211,7 +211,7 @@ export class PDFViewer extends React.Component { mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title); mainAnnoDocProto.annotationOn = this.props.Document; if (sourceDoc && createLink) { - DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`, "", StrCast(this.props.Document.title)); + DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`); } this._savedAnnotations.clear(); this.Index = -1; diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 6bd98cbaa..7ca9d2d7d 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -112,8 +112,7 @@ export default class Page extends React.Component { if (!BoolCast(annotationDoc.linkedToDoc)) { let annotations = await DocListCastAsync(annotationDoc.annotations); annotations && annotations.forEach(anno => anno.target = targetDoc); - let parentDoc = await Cast(annotationDoc.annotationOn, Doc); - parentDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(parentDoc.title)}`, "", StrCast(parentDoc.title)) + DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`) } } }, -- cgit v1.2.3-70-g09d2