Merge pull request #2 from TreetopFlyer/feature/layout-updates

Feature/layout updates
This commit is contained in:
Seth Trowbridge 2023-06-14 22:54:39 -04:00 committed by GitHub
commit a32415f952
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 48 additions and 35 deletions

View File

@ -77,12 +77,29 @@ export const Grade =(inTest)=>
} }
const ErrorCol =
[30, 25, 20, 15, 10, 5, 0, -5, -10, -15 ]
const ErrorLUT = [
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00],
[0.00, 0.00, 0.00, 0.00, 0.10, 0.15, 0.10, 0.10, 0.00, 0.00],
[0.00, 0.00, 0.02, 0.05, 0.15, 0.20, 0.30, 0.15, 0.05, 0.00],
[0.00, 0.02, 0.05, 0.10, 0.20, 0.40, 0.60, 0.30, 0.05, 0.00]
];
/** @type {(inState:Store.State)=>void} */
const ErrorProbability =(inState)=>
{
const miss = inState.Stim.Value - (inState.Live.Mark.Test?.Stim ?? inState.Stim.Value);
inState.Live.Mark.Errs = ErrorLUT[inState.Errs]?.[ErrorCol.indexOf(miss)] ?? 0;
}
/** Creates a new Store.Context object that contain the current selections /** Creates a new Store.Context object that contain the current selections
* @type {(inState:Store.State, inTest?:Store.Test)=>Store.Context} */ * @type {(inState:Store.State, inTest?:Store.Test)=>Store.Context} */
const Reselect =(inState, inTest)=> const Reselect =(inState, inTest)=>
{ {
/** @type {Store.Context} */ /** @type {Store.Context} */
const output = { Test:inTest??inState.Live.Test }; const output = { Test:inTest??inState.Live.Test, Mark:{User:undefined} };
const column = ColumnMapping[inState.Freq.Value]; const column = ColumnMapping[inState.Freq.Value];
if(column && output.Test) if(column && output.Test)
{ {
@ -93,7 +110,8 @@ const Reselect =(inState, inTest)=>
if(plot.Hz == hz) if(plot.Hz == hz)
{ {
output.Freq = plot; output.Freq = plot;
output.Mark = inState.Chan.Value ? plot.UserR : plot.UserL; output.Mark.User = inState.Chan.Value ? plot.UserR : plot.UserL;
output.Mark.Test = inState.Chan.Value ? plot.TestR : plot.TestL;
break; break;
} }
} }
@ -179,8 +197,8 @@ export function Reducer(inState, inAction)
if(clone.Live.Test && clone.Live.Freq) if(clone.Live.Test && clone.Live.Freq)
{ {
const key = clone.Chan.Value == 0 ? "UserL" : "UserR"; const key = clone.Chan.Value == 0 ? "UserL" : "UserR";
clone.Live.Mark = Data !== null ? {Stim:clone.Stim.Value, Resp:Data} : undefined; clone.Live.Mark.User = Data !== null ? {Stim:clone.Stim.Value, Resp:Data} : undefined;
clone.Live.Freq[key] = clone.Live.Mark; clone.Live.Freq[key] = clone.Live.Mark.User;
clone.Draw[key] = Redraw(clone.Live.Test, clone.Chan.Value, clone.Stim, true); clone.Draw[key] = Redraw(clone.Live.Test, clone.Chan.Value, clone.Stim, true);
clone.Live.Test.Done = Grade(clone.Live.Test); clone.Live.Test.Done = Grade(clone.Live.Test);
SaveTests(clone); SaveTests(clone);
@ -227,6 +245,7 @@ export function Reducer(inState, inAction)
clone.Show.Answer = Data; clone.Show.Answer = Data;
} }
ErrorProbability(clone);
SaveSettings(clone); SaveSettings(clone);
return clone; return clone;
@ -328,7 +347,7 @@ export const Initial = Reducer(
{ {
Test: undefined, Test: undefined,
Freq: undefined, Freq: undefined,
Mark: undefined Mark: {User: undefined}
}, },
Draw: Draw:
{ {

View File

@ -5,8 +5,8 @@ import * as Tone from "./tone.js";
/** @typedef {({children, classes}:{children?:preact.ComponentChildren, classes?:string})=>preact.VNode} BasicElement */ /** @typedef {({children, classes}:{children?:preact.ComponentChildren, classes?:string})=>preact.VNode} BasicElement */
/** @type {({children, icon, light, disabled, inactive, onClick, classes}:{children:preact.VNode, icon?:preact.VNode, light:boolean, disabled:boolean, inactive:boolean, onClick:()=>void, classes?:string})=>preact.VNode} */ /** @type {({children, icon, light, disabled, inactive, onClick, classes, classesActive}:{children:preact.VNode, icon?:preact.VNode, light:boolean, disabled:boolean, inactive:boolean, onClick:()=>void, classes?:string, classesActive?:string})=>preact.VNode} */
export function Button({children, icon, light, disabled, inactive, onClick, classes}) export function Button({children, icon, light, disabled, inactive, onClick, classes, classesActive})
{ {
const [FlashGet, FlashSet] = React.useState(0); const [FlashGet, FlashSet] = React.useState(0);
const handleClick =()=> const handleClick =()=>
@ -19,7 +19,7 @@ export function Button({children, icon, light, disabled, inactive, onClick, clas
return html` return html`
<button <button
onClick=${handleClick} onClick=${handleClick}
class="relative flex items-stretch rounded-lg text(lg white) border-t(1 solid [#00000011]) border-b(2 solid [#ffffff]) ring-inset ring-black font-sans group transition-all duration-500 ${classes} ${disabled ? "bg-zinc-400" : "bg-earmark"} ${(inactive||disabled) && "cursor-default"}" class="relative flex items-stretch rounded-lg text(lg white) border-t(1 solid [#00000011]) border-b(2 solid [#ffffff]) ring-inset ring-black font-sans group transition-all duration-500 ${classes} ${disabled ? "bg-zinc-400" : (classesActive||"bg-earmark")} ${(inactive||disabled) && "cursor-default"}"
> >
<span class="absolute top-0 left-0 w-full h-full rounded-lg bg-black transition-opacity duration-300 opacity-0 ${(!inactive && !disabled) && "group-hover:opacity-50"}"></span> <span class="absolute top-0 left-0 w-full h-full rounded-lg bg-black transition-opacity duration-300 opacity-0 ${(!inactive && !disabled) && "group-hover:opacity-50"}"></span>
${ FlashGet > 0 && html`<span key=${FlashGet} class="absolute top-0 left-0 w-full h-full rounded-lg bg-green-400 shadow-glow-green-300 animate-flash"></span>` } ${ FlashGet > 0 && html`<span key=${FlashGet} class="absolute top-0 left-0 w-full h-full rounded-lg bg-green-400 shadow-glow-green-300 animate-flash"></span>` }
@ -58,11 +58,11 @@ export const Header =()=>
</select> </select>
</div> </div>
<div class="box-buttons w-full mt-2"> <div class="box-buttons w-full mt-2">
<p>Patient Error:</p> <p class="px-2">Patient Reliability:</p>
<${Button} inactive=${State.Errs == 0.0} light=${State.Errs == 0.0} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"Errs", Data:0.0})}>None<//> <${Button} inactive=${State.Errs == 0} light=${State.Errs == 0} classes="flex-[1.5] text-xs" classesActive="" onClick=${()=>Dispatch({Name:"Errs", Data:0})}>Perfect (Training Mode)<//>
<${Button} inactive=${State.Errs == 0.1} light=${State.Errs == 0.1} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"Errs", Data:0.1})}>Slight<//> <${Button} inactive=${State.Errs == 1} light=${State.Errs == 1} classes="flex-1 text-xs" classesActive="bg-yellow-600" onClick=${()=>Dispatch({Name:"Errs", Data:1})}>Good<//>
<${Button} inactive=${State.Errs == 0.3} light=${State.Errs == 0.3} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"Errs", Data:0.3})}>Moderate<//> <${Button} inactive=${State.Errs == 2} light=${State.Errs == 2} classes="flex-1 text-xs" classesActive="bg-orange-600" onClick=${()=>Dispatch({Name:"Errs", Data:2})}>Reduced<//>
<${Button} inactive=${State.Errs == 0.6} light=${State.Errs == 0.6} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"Errs", Data:0.6})}>Severe<//> <${Button} inactive=${State.Errs == 3} light=${State.Errs == 3} classes="flex-1 text-xs" classesActive="bg-red-600" onClick=${()=>Dispatch({Name:"Errs", Data:3})}>Poor<//>
</div> </div>
</div> </div>
@ -95,6 +95,7 @@ export const Display =()=>
`; `;
}; };
/** @type {BasicElement} */ /** @type {BasicElement} */
export const Controls =()=> export const Controls =()=>
{ {
@ -113,27 +114,14 @@ export const Controls =()=>
if(State.Live.Freq) if(State.Live.Freq)
{ {
const testMark = State.Live.Freq[/** @type {"TestL"|"TestR"}*/(`Test${State.Chan.Value ? "R":"L"}`)]; const audible = State.Stim.Value >= (State.Live.Mark.Test?.Stim??0);
const audible = State.Stim.Value >= testMark.Stim;
const error = State.Stim.Value - testMark.Stim; const errorScaled = State.Live.Mark.Errs;
let errorMapped = error;
if(error >= 30){ errorMapped = 0.0; }
else if(error >= 25){ errorMapped = 0.1; }
else if(error >= 20){ errorMapped = 0.2; }
else if(error >= 15){ errorMapped = 0.3; }
else if(error >= 10){ errorMapped = 0.5; }
else if(error >= 5){ errorMapped = 0.7; }
else if(error == 0){ errorMapped = 1.0; }
else if(error >= -5){ errorMapped = 0.5; }
else if(error >=-10){ errorMapped = 0.1; }
else if(error >=-15){ errorMapped = 0.0; }
const errorScaled = State.Errs*errorMapped;
const errorSampled = Math.random() < errorScaled; const errorSampled = Math.random() < errorScaled;
const percieved = errorSampled ? !audible : audible const percieved = errorSampled ? !audible : audible;
const handler = percieved ? ()=>playSet(2) : ()=>playSet(0); const handler = percieved ? ()=>playSet(2) : ()=>playSet(0);
console.log("Error:", error, "Error Mapped:", errorMapped, "Error Scaled:", errorScaled, "Error Sampled:", errorSampled); console.log("Audible:", audible, "Error Scaled:", errorScaled, "Error Sampled:", errorSampled, "Percieved", percieved);
timer = setTimeout(handler, 800 + Math.random()*1300); timer = setTimeout(handler, 800 + Math.random()*1300);
} }
} }
@ -205,6 +193,7 @@ export const Controls =()=>
</div> </div>
</div> </div>
</div> </div>
<p class="text-center mt-2">Response${State.Live.Mark.Errs > 0 && ` (${State.Live.Mark.Errs*100}% Error Chance)` }:</p>
<svg width="80" height="80" preserveAspectRatio="none" viewBox="0 0 79 79" fill="none" class="mx-auto mt-2"> <svg width="80" height="80" preserveAspectRatio="none" viewBox="0 0 79 79" fill="none" class="mx-auto mt-2">
<circle fill="url(#metal)" cx="39" cy="40" r="35"></circle> <circle fill="url(#metal)" cx="39" cy="40" r="35"></circle>
<circle fill="url(#metal)" cx="39.5" cy="39.5" r="29.5" transform="rotate(180 39.5 39.5)"></circle> <circle fill="url(#metal)" cx="39.5" cy="39.5" r="29.5" transform="rotate(180 39.5 39.5)"></circle>
@ -267,7 +256,7 @@ export const Controls =()=>
`} `}
onClick=${()=>Dispatch({Name:"Mark", Data:null })} onClick=${()=>Dispatch({Name:"Mark", Data:null })}
classes="text-sm w-full" classes="text-sm w-full"
disabled=${State.Live.Mark == undefined} disabled=${State.Live.Mark.User == undefined}
> >
Clear Clear
<//> <//>
@ -284,9 +273,9 @@ export const Audiogram =()=>
const [State] = Store.Consumer(); 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 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 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.User == 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 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 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.User == p.Mark ? "stroke-bold":""}/>`);
const testLinesL = State.Draw.TestL.Paths.map( p=>html`<line class="opacity-60" x1=${p.Head.X} y1=${p.Head.Y} x2=${p.Tail.X} y2=${p.Tail.Y} />`); const testLinesL = State.Draw.TestL.Paths.map( p=>html`<line class="opacity-60" x1=${p.Head.X} y1=${p.Head.Y} x2=${p.Tail.X} y2=${p.Tail.Y} />`);
const userLinesL = State.Draw.UserL.Paths.map( p=>html`<line class="opacity-60" x1=${p.Head.X} y1=${p.Head.Y} x2=${p.Tail.X} y2=${p.Tail.Y} />`); const userLinesL = State.Draw.UserL.Paths.map( p=>html`<line class="opacity-60" x1=${p.Head.X} y1=${p.Head.Y} x2=${p.Tail.X} y2=${p.Tail.Y} />`);

7
ts/store.d.ts vendored
View File

@ -21,7 +21,12 @@ declare namespace Store {
type Context = { type Context = {
Test?: Test; Test?: Test;
Freq?: TestFrequency; Freq?: TestFrequency;
Mark?: TestFrequencySample; Mark:
{
User?: TestFrequencySample,
Test?: TestFrequencySample,
Errs : number
}
}; };
type StatePartSimple = type StatePartSimple =