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
* @type {(inState:Store.State, inTest?:Store.Test)=>Store.Context} */
const Reselect =(inState, inTest)=>
{
/** @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];
if(column && output.Test)
{
@ -93,7 +110,8 @@ const Reselect =(inState, inTest)=>
if(plot.Hz == hz)
{
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;
}
}
@ -179,8 +197,8 @@ export function Reducer(inState, inAction)
if(clone.Live.Test && clone.Live.Freq)
{
const key = clone.Chan.Value == 0 ? "UserL" : "UserR";
clone.Live.Mark = Data !== null ? {Stim:clone.Stim.Value, Resp:Data} : undefined;
clone.Live.Freq[key] = clone.Live.Mark;
clone.Live.Mark.User = Data !== null ? {Stim:clone.Stim.Value, Resp:Data} : undefined;
clone.Live.Freq[key] = clone.Live.Mark.User;
clone.Draw[key] = Redraw(clone.Live.Test, clone.Chan.Value, clone.Stim, true);
clone.Live.Test.Done = Grade(clone.Live.Test);
SaveTests(clone);
@ -227,6 +245,7 @@ export function Reducer(inState, inAction)
clone.Show.Answer = Data;
}
ErrorProbability(clone);
SaveSettings(clone);
return clone;
@ -328,7 +347,7 @@ export const Initial = Reducer(
{
Test: undefined,
Freq: undefined,
Mark: undefined
Mark: {User: undefined}
},
Draw:
{

View File

@ -5,8 +5,8 @@ import * as Tone from "./tone.js";
/** @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} */
export function Button({children, icon, light, disabled, inactive, onClick, classes})
/** @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, classesActive})
{
const [FlashGet, FlashSet] = React.useState(0);
const handleClick =()=>
@ -19,7 +19,7 @@ export function Button({children, icon, light, disabled, inactive, onClick, clas
return html`
<button
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>
${ 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>
</div>
<div class="box-buttons w-full mt-2">
<p>Patient Error:</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.1} light=${State.Errs == 0.1} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"Errs", Data:0.1})}>Slight<//>
<${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 == 0.6} light=${State.Errs == 0.6} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"Errs", Data:0.6})}>Severe<//>
<p class="px-2">Patient Reliability:</p>
<${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 == 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 == 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 == 3} light=${State.Errs == 3} classes="flex-1 text-xs" classesActive="bg-red-600" onClick=${()=>Dispatch({Name:"Errs", Data:3})}>Poor<//>
</div>
</div>
@ -95,6 +95,7 @@ export const Display =()=>
`;
};
/** @type {BasicElement} */
export const Controls =()=>
{
@ -113,27 +114,14 @@ export const Controls =()=>
if(State.Live.Freq)
{
const testMark = State.Live.Freq[/** @type {"TestL"|"TestR"}*/(`Test${State.Chan.Value ? "R":"L"}`)];
const audible = State.Stim.Value >= testMark.Stim;
const audible = State.Stim.Value >= (State.Live.Mark.Test?.Stim??0);
const error = State.Stim.Value - testMark.Stim;
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 errorScaled = State.Live.Mark.Errs;
const errorSampled = Math.random() < errorScaled;
const percieved = errorSampled ? !audible : audible
const percieved = errorSampled ? !audible : audible;
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);
}
}
@ -205,6 +193,7 @@ export const Controls =()=>
</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">
<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>
@ -267,7 +256,7 @@ export const Controls =()=>
`}
onClick=${()=>Dispatch({Name:"Mark", Data:null })}
classes="text-sm w-full"
disabled=${State.Live.Mark == undefined}
disabled=${State.Live.Mark.User == undefined}
>
Clear
<//>
@ -284,9 +273,9 @@ 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 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 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 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 = {
Test?: Test;
Freq?: TestFrequency;
Mark?: TestFrequencySample;
Mark:
{
User?: TestFrequencySample,
Test?: TestFrequencySample,
Errs : number
}
};
type StatePartSimple =