From e0316c21838613df0fbf43df6a9ca5d696c52f47 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 30 Jul 2019 20:19:58 -0400
Subject: more interesting speech commands and command manager pattern for
 DictationManager

---
 src/client/views/collections/CollectionBaseView.tsx | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

(limited to 'src/client/views/collections/CollectionBaseView.tsx')

diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index c595a4c56..3f88ed98c 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -22,6 +22,24 @@ export enum CollectionViewType {
     Masonry
 }
 
+export namespace CollectionViewType {
+
+    const stringMapping = new Map<string, CollectionViewType>([
+        ["invalid", CollectionViewType.Invalid],
+        ["freeform", CollectionViewType.Freeform],
+        ["schema", CollectionViewType.Schema],
+        ["docking", CollectionViewType.Docking],
+        ["tree", CollectionViewType.Tree],
+        ["stacking", CollectionViewType.Stacking],
+        ["masonry", CollectionViewType.Masonry]
+    ]);
+
+    export const ValueOf = (value: string) => {
+        return stringMapping.get(value.toLowerCase());
+    };
+
+}
+
 export interface CollectionRenderProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
     removeDocument: (document: Doc) => boolean;
-- 
cgit v1.2.3-70-g09d2


From 982961c71fdd9d5dcd02ea33a2b631076a6a1f4b Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
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/collections/CollectionBaseView.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, 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);
     }
 
-- 
cgit v1.2.3-70-g09d2

