import React from "react";
import { html } from "htm";
import * as Store from "./store.js";
import * as Tone from "./tone.js";
/** @typedef {({children}:{children?:preact.ComponentChildren})=>preact.VNode} BasicElement */
/** @type {({children, icon, light, disabled, inactive, onClick}:{children:preact.VNode, icon?:preact.VNode, light:boolean, disabled:boolean, inactive:boolean, onClick:()=>void})=>preact.VNode} */
export function Button({children, icon, light, disabled, inactive, onClick})
{
const [FlashGet, FlashSet] = React.useState(0);
const handleClick =()=>
{
if(inactive||disabled){ return; }
FlashSet(FlashGet+1);
onClick();
};
return html`
`;
}
/** @type {BasicElement} */
export const Select =()=>
{
const [State, Dispatch] = Store.Consumer();
/** @type {(e:Event)=>void} */
const handleChange =(e)=> Dispatch({Name:"Test", Data:parseInt(/** @type {HTMLSelectElement}*/(e.target).value)});
return html`
`;
}
/** @type {BasicElement} */
export const Grade =()=>
{
const [State] = Store.Consumer();
const grade = Store.Grade(State.Live.Test);
return html`
Complete: ${grade.Done} of ${grade.Total}
Accuracy: ${grade.Score}%
`
};
/** @type {BasicElement} */
export const Controls =()=>
{
const [State, Dispatch] = Store.Consumer();
const [pulsedGet, pulsedSet] = React.useState(true);
const [playGet, playSet] = React.useState(0);
React.useEffect(()=>
{
/** @type {number|undefined} */
let timer;
if(playGet == 1)
{
const volNorm = (State.Stim.Value-State.Stim.Min)/(State.Stim.Max-State.Stim.Min);
Tone.Play(!pulsedGet, State.Chan.Value, Store.ColumnMapping[State.Freq.Value][0], (volNorm*0.8) + 0.1);
if(State.Live.Freq)
{
const testMark = State.Live.Freq[/** @type {"TestL"|"TestR"}*/(`Test${State.Chan.Value ? "R":"L"}`)];
const handler = testMark.Stim <= State.Stim.Value ? ()=>{playSet(2)} : ()=>{playSet(0)}
timer = setTimeout(handler, 500 + Math.random()*1000);
}
}
return () => clearTimeout(timer);
}, [playGet]);
return html`
<${Grade}/>
Channel
${State.Chan.Value}
<${Button} light=${State.Chan.Value == 0} inactive=${State.Chan.Value == 0} onClick=${()=>Dispatch({Name:"Chan", Data:-1})}>Left/>
<${Button} light=${State.Chan.Value == 1} inactive=${State.Chan.Value == 1} onClick=${()=>Dispatch({Name:"Chan", Data:1})}>Right/>
Frequency
${Store.ColumnMapping[State.Freq.Value][0]}
<${Button} disabled=${State.Freq.Value == State.Freq.Min} onClick=${()=>Dispatch({Name:"Freq", Data:-1})}>-/>
<${Button} disabled=${State.Freq.Value == State.Freq.Max} onClick=${()=>Dispatch({Name:"Freq", Data:1})}>+/>
Stimulus
${State.Stim.Value}
<${Button} disabled=${State.Stim.Value == State.Stim.Min} onClick=${()=>Dispatch({Name:"Stim", Data:-1})}>-/>
<${Button} disabled=${State.Stim.Value == State.Stim.Max} onClick=${()=>Dispatch({Name:"Stim", Data:1})}>+/>
Tone
<${Button} onClick=${()=>{pulsedSet(true)}} light=${pulsedGet} inactive${pulsedGet}>Pulsed/>
<${Button} onClick=${()=>{pulsedSet(false)}} light=${!pulsedGet} inactive${!pulsedGet}>Continuous/>
Present
<${Button} onClick=${()=>playSet(1)} disabled=${playGet==1} icon=${html`
`}>Play/>
Mark
<${Button} onClick=${()=>Dispatch({Name:"Mark", Data:true })} icon=${html`<${Mark} right=${State.Chan.Value} response=${true} x="0" y="0" classes="stroke(white 2 draw) w-2 h-2 translate-x-1/2 translate-y-1/2"/>`}>Response/>
<${Button} onClick=${()=>Dispatch({Name:"Mark", Data:false})} icon=${html`<${Mark} right=${State.Chan.Value} response=${false} x="0" y="0" classes="stroke(white 2 draw) w-2 h-2 translate-x-1/2 translate-y-1/2"/>`}>No Response/>
<${Button}
icon=${html`
`}
onClick=${()=>Dispatch({Name:"Mark", Data:null })}
disabled=${State.Live.Mark == undefined}>Clear/>
`;
};
/** @type {BasicElement} */
export const Audiogram =()=>
{
const [State] = Store.Consumer();
const testMarksL = State.Draw.TestL.Points.map(p=>html`<${Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${false}/>`);
const userMarksL = State.Draw.UserL.Points.map(p=>html`<${Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${false} classes=${State.Live.Mark == p.Mark ? "stroke-bold":""}/>`);
const testMarksR = State.Draw.TestR.Points.map(p=>html`<${Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${true} />`);
const userMarksR = State.Draw.UserR.Points.map(p=>html`<${Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${true} classes=${State.Live.Mark == p.Mark ? "stroke-bold":""}/>`);
const testLinesL = State.Draw.TestL.Paths.map( p=>html``);
const userLinesL = State.Draw.UserL.Paths.map( p=>html``);
const testLinesR = State.Draw.TestR.Paths.map( p=>html``);
const userLinesR = State.Draw.UserR.Paths.map( p=>html``);
return html`
`;
};
/** @type {BasicElement} */
export function Chart({children})
{
const [State] = Store.Consumer();
const inset = 20;
/** @type {Array} */
const rules = [];
Store.ColumnMapping.forEach(([label, position, normal])=>
{
rules.push(html`
${label}
`
);
});
for(let db = State.Stim.Min; db <= State.Stim.Max; db+=10)
{
rules.push(html`
${db}
`
);
}
return html`
Frequency in Hz
Hearing Level (dbHL)
`;
}
/** @type {Record} */
const Glyph = {
Arrow:()=> html`
`,
X: ({children})=> html`
${children}`,
O: ({children})=> html`
${children}`
};
/** @type {({right, response, x, y, classes}:{right:boolean, response?:boolean, x:number|string, y:number|string, classes:string})=>preact.VNode} */
export const Mark =({right, response, x, y, classes})=>
{
return html`
`;
};