From 16cc5443149e44e1ba52d5c33f006e398e652ea5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 20 Apr 2018 20:16:59 +0200 Subject: [PATCH] Added search by voice input --- layout/sidebar/sidebar.pixy | 2 + layout/sidebar/sidebar.scarlet | 13 +++++ scripts/Actions/Search.ts | 50 ++++++++++++++++- scripts/Types/WebSpeechAPI.d.ts | 96 +++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 scripts/Types/WebSpeechAPI.d.ts diff --git a/layout/sidebar/sidebar.pixy b/layout/sidebar/sidebar.pixy index fdf6af08..aa317033 100644 --- a/layout/sidebar/sidebar.pixy +++ b/layout/sidebar/sidebar.pixy @@ -21,6 +21,8 @@ component Sidebar(user *arn.User) .sidebar-button Icon("search") FuzzySearch + .speech-input.action(data-action="searchBySpeech", data-trigger="click", title="Voice input") + RawIcon("microphone") //- Sidebar buttons if user != nil diff --git a/layout/sidebar/sidebar.scarlet b/layout/sidebar/sidebar.scarlet index 0c57f966..acb33085 100644 --- a/layout/sidebar/sidebar.scarlet +++ b/layout/sidebar/sidebar.scarlet @@ -102,3 +102,16 @@ const sidebar-spacing-y = 0.7rem background badge-important-hover-bg-color color badge-important-text-color text-shadow none + +// Microphone icon +.speech-input + opacity 0.5 + default-transition + + :hover + cursor pointer + opacity 1 + +.speech-listening + color link-hover-color + opacity 1 diff --git a/scripts/Actions/Search.ts b/scripts/Actions/Search.ts index 8aa93bae..98523410 100644 --- a/scripts/Actions/Search.ts +++ b/scripts/Actions/Search.ts @@ -34,8 +34,8 @@ const fetchOptions: RequestInit = { } // Search -export async function search(arn: AnimeNotifier, search: HTMLInputElement, e: KeyboardEvent) { - if(e.ctrlKey || e.altKey) { +export async function search(arn: AnimeNotifier, search: HTMLInputElement, evt?: KeyboardEvent) { + if(evt && (evt.ctrlKey || evt.altKey)) { return } @@ -177,3 +177,49 @@ export function showSearchResults(arn: AnimeNotifier, element: HTMLElement) { arn.mountMountables(findAllInside("mountable", element)) arn.assignTooltipOffsets(findAllInside("tip", element)) } + +export function searchBySpeech(arn: AnimeNotifier, element: HTMLElement) { + let searchInput = document.getElementById("search") as HTMLInputElement + let oldPlaceholder = searchInput.placeholder + let SpeechRecognition: SpeechRecognitionStatic = (window["SpeechRecognition"] || window["webkitSpeechRecognition"]) + let recognition = new SpeechRecognition() + recognition.continuous = false + recognition.interimResults = false + recognition.lang = "en-US" + + recognition.onresult = evt => { + if(evt.results.length > 0) { + let result = evt.results.item(0).item(0) + let term = result.transcript + + if(term !== "") { + searchInput.value = term + arn.sideBar.hide() + search(arn, searchInput) + } + } + + recognition.stop() + } + + recognition.onerror = e => { + recognition.stop() + } + + recognition.onend = e => { + searchInput.placeholder = oldPlaceholder + element.classList.remove("speech-listening") + } + + // Focus search field + searchInput.placeholder = "Listening..." + searchInput.value = "" + searchInput.focus() + searchInput.select() + + // Highlight microphone icon + element.classList.add("speech-listening") + + // Start voice recognition + recognition.start() +} diff --git a/scripts/Types/WebSpeechAPI.d.ts b/scripts/Types/WebSpeechAPI.d.ts new file mode 100644 index 00000000..a7aae159 --- /dev/null +++ b/scripts/Types/WebSpeechAPI.d.ts @@ -0,0 +1,96 @@ +// Type definitions for Web Speech API +// Project: https://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html +// Definitions by: SaschaNaz +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.2 + +// Spec version: 19 October 2012 +// Errata version: 6 June 2014 +// Corrected unofficial spec version: 6 June 2014 + +interface SpeechRecognition extends EventTarget { + grammars: SpeechGrammarList; + lang: string; + continuous: boolean; + interimResults: boolean; + maxAlternatives: number; + serviceURI: string; + + start(): void; + stop(): void; + abort(): void; + + onaudiostart: (ev: Event) => any; + onsoundstart: (ev: Event) => any; + onspeechstart: (ev: Event) => any; + onspeechend: (ev: Event) => any; + onsoundend: (ev: Event) => any; + onaudioend: (ev: Event) => any; + onresult: (ev: SpeechRecognitionEvent) => any; + onnomatch: (ev: SpeechRecognitionEvent) => any; + onerror: (ev: SpeechRecognitionError) => any; + onstart: (ev: Event) => any; + onend: (ev: Event) => any; +} +interface SpeechRecognitionStatic { + prototype: SpeechRecognition; + new (): SpeechRecognition; +} +declare var SpeechRecognition: SpeechRecognitionStatic; +declare var webkitSpeechRecognition: SpeechRecognitionStatic; + +interface SpeechRecognitionError extends Event { + error: string; + message: string; +} + +interface SpeechRecognitionAlternative { + transcript: string; + confidence: number; +} + +interface SpeechRecognitionResult { + length: number; + item(index: number): SpeechRecognitionAlternative; + [index: number]: SpeechRecognitionAlternative; + /* Errata 02 */ + isFinal: boolean; +} + +interface SpeechRecognitionResultList { + length: number; + item(index: number): SpeechRecognitionResult; + [index: number]: SpeechRecognitionResult; +} + +interface SpeechRecognitionEvent extends Event { + resultIndex: number; + results: SpeechRecognitionResultList; + interpretation: any; + emma: Document; +} + +interface SpeechGrammar { + src: string; + weight: number; +} +interface SpeechGrammarStatic { + prototype: SpeechGrammar; + new (): SpeechGrammar; +} +declare var SpeechGrammar: SpeechGrammarStatic; +declare var webkitSpeechGrammar: SpeechGrammarStatic; + +interface SpeechGrammarList { + length: number; + item(index: number): SpeechGrammar; + [index: number]: SpeechGrammar; + addFromURI(src: string, weight: number): void; + addFromString(string: string, weight: number): void; +} +interface SpeechGrammarListStatic { + prototype: SpeechGrammarList; + new (): SpeechGrammarList; +} +declare var SpeechGrammarList: SpeechGrammarListStatic; +declare var webkitSpeechGrammarList: SpeechGrammarListStatic;