aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/util/DictationManager.ts307
-rw-r--r--src/client/views/GlobalKeyHandler.ts14
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx4
4 files changed, 165 insertions, 162 deletions
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, CastCtor>([
- [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<any>;
-export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
-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<string>((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<string>((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<any>;
+ export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] };
+
+ export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
+ 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, CastCtor>([
+ [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<string, RegistrationEntry>([
+ 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<string, IndependentEntry>([
- ["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<RegexEntry>(
-
- {
- 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<DependentEntry>(
+
+ {
+ 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<KeyControlInfo>;
@@ -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<DocumentViewProps, Document>(Docu
}
listen = async () => {
- let transcript = await DictationManager.Instance.listen();
+ let transcript = await DictationManager.Controls.listen();
transcript && (Doc.GetProto(this.props.Document).transcript = transcript);
}