-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
10 changed files
with
401 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import * as React from "react" | ||
|
||
import { cn } from "@/lib/utils" | ||
|
||
export interface TextareaProps | ||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} | ||
|
||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>( | ||
({ className, ...props }, ref) => { | ||
return ( | ||
<textarea | ||
className={cn( | ||
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", | ||
className | ||
)} | ||
ref={ref} | ||
{...props} | ||
/> | ||
) | ||
} | ||
) | ||
Textarea.displayName = "Textarea" | ||
|
||
export { Textarea } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { lazy, Suspense, useEffect, useState } from 'react'; | ||
import { useSpeech } from '../../../../src/'; | ||
import { packageName } from '@/lib/utils'; | ||
import Muted from '@/common/Muted'; | ||
import { Button } from '@/components/ui/button'; | ||
import { Textarea } from "@/components/ui/textarea" | ||
|
||
const Title = lazy(() => import('@/common/Details/Title')); | ||
const Block = lazy(() => import('@/common/Details/Block')); | ||
const Api = lazy(() => import('@/common/Details/Api')); | ||
const Lead = lazy(() => import('@/common/Lead')); | ||
|
||
const hook = 'useSpeech'; | ||
|
||
const api = [ | ||
{ execute: 'loadSpeech', type: 'function', description: 'This function accepts 2 parameters, 1st of is HTMLElement, 2nd one is string, you can pass anyone of them at a time' }, | ||
{ execute: 'play', type: 'function', description: 'Call this function to start the speech, before calling this function, make sure you call loadSpeech first' }, | ||
{ execute: 'resume', type: 'function', description: 'Call this function to resume the speech' }, | ||
{ execute: 'pause', type: 'function', description: 'Call this function to pause the speech' }, | ||
{ execute: 'stop', type: 'function', description: 'Call this function to stop the speech' }, | ||
{ execute: 'currentPlayingState', type: 'string', description: 'Current playing state among these - initialized | playing | paused | ended | resumed' }, | ||
]; | ||
|
||
export default function SpeechComponent() { | ||
|
||
const [text, setText] = useState("Lorem ipsum dolor sit amet consectetur adipisicing elit. Cum nam veritatis incidunt debitis. Repellendus at corrupti odit, nam deleniti cum fugiat temporibus libero suscipit harum fugit aliquid saepe minima ipsam."); | ||
const { play, resume, pause, stop, loadSpeech, currentPlayingState } = useSpeech(); | ||
|
||
return ( | ||
<Suspense fallback={<></>}> | ||
|
||
<Title>{hook}</Title> | ||
|
||
<Lead className='text-md'> | ||
The <code>{hook}</code> will help you implement speeching facility | ||
</Lead> | ||
|
||
<Block title='Usage'> | ||
<div className='flex flex-col gap-5'> | ||
<code> | ||
{`import { ${hook} } from "${packageName}";`} | ||
</code> | ||
|
||
<code> | ||
{`const { loadSpeech, play, resume, pause, stop, currentPlayingState } = ${hook}();`} | ||
</code> | ||
</div> | ||
</Block> | ||
|
||
<Api api={api} /> | ||
|
||
<Block title='Example'> | ||
|
||
<Textarea | ||
defaultValue={text} | ||
className='mb-4' | ||
onChange={(e) => { | ||
setText(e.target.value); | ||
}} /> | ||
|
||
<div className="flex gap-3 mb-2"> | ||
<Button size="sm" disabled={!(currentPlayingState === 'initialized')} | ||
onClick={() => { | ||
loadSpeech(document.getElementById('text-to-speech-content'), text); | ||
play(); | ||
}}>Play</Button> | ||
|
||
<Button size="sm" disabled={!(['playing', 'resumed'].includes(currentPlayingState))} | ||
onClick={() => { | ||
pause(); | ||
}}>Pause</Button> | ||
|
||
<Button size="sm" disabled={currentPlayingState !== 'paused'} | ||
onClick={() => { | ||
resume(); | ||
}}>Resume</Button> | ||
|
||
<Button size="sm" variant="destructive" | ||
disabled={!(['playing', 'resumed', 'paused'].includes(currentPlayingState))} | ||
onClick={() => { | ||
stop(); | ||
}}>Stop</Button> | ||
</div> | ||
|
||
<div className='flex justify-between flex-col items-start gap-3'> | ||
|
||
<div id="text-to-speech-content" | ||
dangerouslySetInnerHTML={{ | ||
__html: text | ||
}} | ||
> | ||
</div> | ||
</div> | ||
|
||
</Block> | ||
|
||
</Suspense> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
## useSpeech | ||
|
||
### This hook will help you implement speeching facility | ||
|
||
<br /> | ||
|
||
> Example | ||
```jsx | ||
import { useState } from 'react'; | ||
import { useSpeech } from 'react-helper-hooks'; | ||
|
||
export default function SpeechComponent() { | ||
const { loadSpeech, play, resume, pause, stop, currentPlayingState } = useSpeech(); | ||
const [text, setText] = useState(""); | ||
|
||
return ( | ||
<div> | ||
|
||
<textarea | ||
value={text} | ||
onChange={(e) => { | ||
setText(e.target.value); | ||
}} | ||
></textarea> | ||
|
||
<button type="button" disabled={!(currentPlayingState === 'initialized')} | ||
onClick={() => { | ||
loadSpeech(document.getElementById('text-to-speech-content'), text); | ||
play(); | ||
}}>Play</button> | ||
|
||
<button type="button" disabled={!(['playing', 'resumed'].includes(currentPlayingState))} | ||
onClick={() => { | ||
pause(); | ||
}}>Pause</button> | ||
|
||
<button type="button" disabled={currentPlayingState !== 'paused'} | ||
onClick={() => { | ||
resume(); | ||
}}>Resume</button> | ||
|
||
<button type="button" | ||
disabled={!(['playing', 'resumed', 'paused'].includes(currentPlayingState))} | ||
onClick={() => { | ||
stop(); | ||
}}>Stop</button> | ||
|
||
<br /> | ||
|
||
<div id="text-to-speech-content" | ||
dangerouslySetInnerHTML={{ | ||
__html: text | ||
}} | ||
> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { PlayingState } from './utils'; | ||
|
||
export type SpeechEngineOptions = { | ||
onBoundary: (e: SpeechSynthesisEvent) => void; | ||
onEnd: (e: SpeechSynthesisEvent) => void; | ||
onResume: (e: SpeechSynthesisEvent) => void; | ||
onStateUpdate: (state: PlayingState) => void; | ||
}; | ||
|
||
export type SpeechEngineState = { | ||
utterance: SpeechSynthesisUtterance | null; | ||
config: { | ||
rate: number; | ||
volume: number; | ||
voice: SpeechSynthesisVoice; | ||
}; | ||
}; | ||
|
||
export type SpeechEngine = ReturnType<typeof createSpeechEngine>; | ||
|
||
/** | ||
* This speech engine is meant to be a simple adapter for using speech synthesis api. | ||
* This should generally be left for the candidate to use as the speech synthesis apis have a few nuances | ||
* that the candidate might not be familiar with. | ||
*/ | ||
const createSpeechEngine = (options: SpeechEngineOptions) => { | ||
const state: SpeechEngineState = { | ||
utterance: null, | ||
config: { | ||
rate: 1, | ||
volume: 1, | ||
voice: window.speechSynthesis.getVoices()[0], | ||
}, | ||
}; | ||
|
||
window.speechSynthesis.onvoiceschanged = (e) => { | ||
state.config.voice = speechSynthesis.getVoices()[0]; | ||
}; | ||
|
||
const load = (text: string) => { | ||
const utterance = new SpeechSynthesisUtterance(text); | ||
utterance.rate = state.config.rate; | ||
utterance.volume = state.config.volume; | ||
utterance.voice = state.config.voice; | ||
// set up listeners | ||
utterance.onboundary = (e) => options.onBoundary(e); | ||
utterance.onend = (e) => { | ||
options.onStateUpdate('ended'); | ||
options.onEnd(e); | ||
}; | ||
|
||
// set it up as active utterance | ||
state.utterance = utterance; | ||
}; | ||
|
||
const play = () => { | ||
if (!state.utterance) throw new Error('No active utterance found to play'); | ||
state.utterance.onstart = () => { | ||
options.onStateUpdate('playing'); | ||
}; | ||
window.speechSynthesis.cancel(); | ||
window.speechSynthesis.speak(state.utterance); | ||
}; | ||
|
||
const pause = () => { | ||
options.onStateUpdate('paused'); | ||
window.speechSynthesis.pause(); | ||
}; | ||
|
||
const cancel = () => { | ||
options.onStateUpdate('initialized'); | ||
window.speechSynthesis.cancel(); | ||
}; | ||
|
||
const resume = () => { | ||
options.onStateUpdate('resumed'); | ||
window.speechSynthesis.resume(); | ||
}; | ||
|
||
return { | ||
state, | ||
play, | ||
resume, | ||
pause, | ||
cancel, | ||
load, | ||
}; | ||
}; | ||
|
||
export { createSpeechEngine }; |
Oops, something went wrong.