diff --git a/deno.json b/deno.json index 6997ddd..538cf72 100644 --- a/deno.json +++ b/deno.json @@ -1,15 +1,9 @@ { - "compilerOptions": { "types": ["./store"], "checkJs": true }, - "importMap": "./deno.map.json", - "fmt": { - "options": { - "lineWidth": 256 - } - }, - "tasks": { - "dev": "deno task serve & deno task test", - "fmt": "deno fmt --watch", - "serve": "deno run -A --unstable --no-check https://deno.land/std@0.167.0/http/file_server.ts", - "test": "deno test store.test.ts --watch --no-lock --no-check" + "compilerOptions": { "types": ["*.d.ts"], "checkJs": true }, + "imports": { + "@twind/": "https://esm.sh/@twind/", + "react": "https://esm.sh/preact@10.11.3/compat", + "htm": "https://esm.sh/htm@3.1.1/preact", + "app": "./js/app.js" } -} +} \ No newline at end of file diff --git a/deno.map.json b/deno.map.json deleted file mode 100644 index 65001b1..0000000 --- a/deno.map.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "imports": { - "@twind/": "https://esm.sh/@twind/", - "react": "https://esm.sh/preact@10.11.3/compat", - "htm": "https://esm.sh/htm@3.1.1/preact", - "app": "./src/app.js" - } -} diff --git a/index.html b/index.html index 11a419f..c9e0a2a 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,12 @@ - - - -
- - - \ No newline at end of file + + + + + + +
+ + + + + \ No newline at end of file diff --git a/src/app.js b/js/app.js similarity index 50% rename from src/app.js rename to js/app.js index 54c6113..1c84348 100644 --- a/src/app.js +++ b/js/app.js @@ -15,23 +15,20 @@ TW.Init(ShadowCSS, ShadowDiv); React.render(html` <${Store.Provider}> +
-
-
- -
-
- <${UI.Select}/> -
-
- <${UI.Controls}/> -
-
- <${UI.Chart}> - <${UI.Audiogram}/> - -
+ <${UI.Header}/> + +
+ <${UI.Controls}/> + <${UI.Chart}> + <${UI.Audiogram}/> +
<${UI.Display}/>
+
+ + +
`, ShadowDiv); \ No newline at end of file diff --git a/src/store.js b/js/store.js similarity index 51% rename from src/store.js rename to js/store.js index 66e8e85..1d4fa3b 100644 --- a/src/store.js +++ b/js/store.js @@ -28,6 +28,55 @@ export const ColumnLookup =(inFrequency)=> /** @type {(inDecimal:number)=>string} */ const Perc =(inDecimal)=> `${inDecimal*100}%`; +/** @type {(inTest:Store.Test)=>Store.Grade} */ +export const Grade =(inTest)=> +{ + /** @type {Store.Grade} */ + const output = { Total:0, Marks:0, Score:0 }; + + /** @type {(inGoal:number, inResult:number)=>number} */ + const Mapper =(inGoal, inResult)=> + { + const err = Math.abs(inGoal-inResult); + if(err == 0){ return 1; } + else if(err > 0 && err <= 5){ return 0.9; } + else if(err > 5 && err <= 10){ return 0.7; } + else if(err > 10 && err <= 15){ return 0.2; } + else{ return 0; } + } + + + for(let i=0; i 0) + { + output.Score = Math.floor((output.Score/output.Marks) * 10000)/100; + } + + return output; + +} + /** Creates a new Store.Context object that contain the current selections * @type {(inState:Store.State, inTest?:Store.Test)=>Store.Context} */ const Reselect =(inState, inTest)=> @@ -44,7 +93,8 @@ const Reselect =(inState, inTest)=> if(plot.Hz == hz) { output.Freq = plot; - output.Mark = plot[`User${inState.Chan.Value ? "R" : "L"}`]; + output.Mark = inState.Chan.Value ? plot.UserR : plot.UserL; + break; } } } @@ -111,7 +161,7 @@ export function Reducer(inState, inAction) const test = clone.Test[Data]; if(test) { - clone.TestIndex = Data; + clone.Pick = Data; clone.Live = Reselect(clone, test); clone.Draw = { @@ -121,6 +171,7 @@ export function Reducer(inState, inAction) TestL: Redraw(test, 0, clone.Stim, false), TestR: Redraw(test, 1, clone.Stim, false) }; + test.Done = Grade(test); } } else if (Name == "Mark") @@ -131,6 +182,8 @@ export function Reducer(inState, inAction) clone.Live.Mark = Data !== null ? {Stim:clone.Stim.Value, Resp:Data} : undefined; clone.Live.Freq[key] = clone.Live.Mark; clone.Draw[key] = Redraw(clone.Live.Test, clone.Chan.Value, clone.Stim, true); + clone.Live.Test.Done = Grade(clone.Live.Test); + SaveTests(clone); } } else if( Name=="Stim" || Name=="Chan" || Name=="Freq") @@ -146,6 +199,25 @@ export function Reducer(inState, inAction) clone.Live = Reselect(clone); } } + else if (Name == "Errs") + { + clone.Errs = Data; + } + else if (Name == "Kill") + { + if(clone.Live.Test) + { + clone.Live.Test.Plot.forEach(freq=> + { + freq.UserL = undefined; + freq.UserR = undefined; + }); + clone.Draw["UserL"] = Redraw(clone.Live.Test, clone.Chan.Value, clone.Stim, true); + clone.Draw["UserR"] = Redraw(clone.Live.Test, clone.Chan.Value, clone.Stim, true); + clone.Live.Test.Done = Grade(clone.Live.Test); + SaveTests(clone); + } + } else if (Name == "ShowCursor") { clone.Show.Cursor = Data; @@ -155,15 +227,103 @@ export function Reducer(inState, inAction) clone.Show.Answer = Data; } + SaveSettings(clone); + return clone; } -/** @type {Store.State} */ -export const Initial = Reducer( + +/** @type {Store.Test[]} */ +const TestDefault = [ + { + Name: "Patient A Asymmetric Notch", + Plot: + [ + { Hz: 500, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 10, Resp: true } }, + { Hz: 1000, TestL: { Stim: 10, Resp: true }, TestR: { Stim: 10, Resp: true } }, + { Hz: 2000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 20, Resp: true } }, + { Hz: 3000, TestL: { Stim: 30, Resp: true }, TestR: { Stim: 40, Resp: true } }, + { Hz: 4000, TestL: { Stim: 40, Resp: true }, TestR: { Stim: 55, Resp: true } }, + { Hz: 6000, TestL: { Stim: 35, Resp: true }, TestR: { Stim: 40, Resp: true } }, + { Hz: 8000, TestL: { Stim: 20, Resp: true }, TestR: { Stim: 15, Resp: true } } + ] + }, + { + Name: "Patient B High Freq Hearing Loss", + Plot: + [ + { Hz: 500, TestL: { Stim: 10, Resp: true }, TestR: { Stim: 10, Resp: true } }, + { Hz: 1000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 10, Resp: true } }, + { Hz: 2000, TestL: { Stim: 10, Resp: true }, TestR: { Stim: 15, Resp: true } }, + { Hz: 3000, TestL: { Stim: 25, Resp: true }, TestR: { Stim: 20, Resp: true } }, + { Hz: 4000, TestL: { Stim: 35, Resp: true }, TestR: { Stim: 35, Resp: true } }, + { Hz: 6000, TestL: { Stim: 50, Resp: true }, TestR: { Stim: 55, Resp: true } }, + { Hz: 8000, TestL: { Stim: 80, Resp: true }, TestR: { Stim: 75, Resp: true } } + ] + }, + { + Name: "Patient C Unilateral Hearing Loss", + Plot: + [ + { Hz: 500, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 40, Resp: true } }, + { Hz: 1000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 50, Resp: true } }, + { Hz: 2000, TestL: { Stim: 20, Resp: true }, TestR: { Stim: 65, Resp: true } }, + { Hz: 3000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 70, Resp: true } }, + { Hz: 4000, TestL: { Stim: 20, Resp: true }, TestR: { Stim: 65, Resp: true } }, + { Hz: 6000, TestL: { Stim: 25, Resp: true }, TestR: { Stim: 60, Resp: true } }, + { Hz: 8000, TestL: { Stim: 20, Resp: true }, TestR: { Stim: 45, Resp: true } } + ] + }, + { + Name: "Patient D Normal Hearing", + Plot: + [ + { Hz: 500, TestL: { Stim: 5, Resp: true }, TestR: { Stim: 10, Resp: true } }, + { Hz: 1000, TestL: { Stim: 0, Resp: true }, TestR: { Stim: 5, Resp: true } }, + { Hz: 2000, TestL: { Stim: 5, Resp: true }, TestR: { Stim: 5, Resp: true } }, + { Hz: 3000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 10, Resp: true } }, + { Hz: 4000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 15, Resp: true } }, + { Hz: 6000, TestL: { Stim: 5, Resp: true }, TestR: { Stim: 10, Resp: true } }, + { Hz: 8000, TestL: { Stim: 0, Resp: true }, TestR: { Stim: 5, Resp: true } } + ] + } +]; +/** @type {Store.Test[]} */ +const TestActual = JSON.parse(localStorage.getItem("app-tests")||"false") || TestDefault; +/**@type {(inState:Store.State)=>void} */ +const SaveTests =(inState)=> localStorage.setItem("app-tests", JSON.stringify(inState.Test)); + +/** @type {Store.StatePartSimple} */ +const SettingsDefault = { Chan: { Min:0, Max:1, Value:0, Step:1 }, Freq: { Min:2, Max:8, Value:3, Step:1 }, Stim: { Min:-10, Max:120, Value:30, Step:5 }, + Errs: 0, + Pick: 0, + Show: { Cursor:true, Answer:false } +}; +/** @type {Store.StatePartSimple} */ +const SettingsActual = JSON.parse(localStorage.getItem("app-settings")||"false") || SettingsDefault; +/**@type {(inState:Store.State)=>void} */ +const SaveSettings =(inState)=> +{ + /** @type {Store.StatePartSimple} */ + const clone = { + Chan:inState.Chan, + Freq:inState.Freq, + Stim:inState.Stim, + Errs:inState.Errs, + Pick:inState.Pick, + Show:inState.Show + }; + localStorage.setItem("app-settings", JSON.stringify(clone)); +}; + +export const Initial = Reducer( +{ + ...SettingsActual, + Test: TestActual, Live: { Test: undefined, @@ -176,71 +336,11 @@ export const Initial = Reducer( UserR:{Points:[], Paths:[]}, TestL:{Points:[], Paths:[]}, TestR:{Points:[], Paths:[]} - }, - Show: - { - Cursor:true, - Answer:false - }, - TestIndex: 0, - Test: [ - { - Name: "Patient A Asymmetric Notch", - Plot: - [ - { Hz: 500, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 10, Resp: true } }, - { Hz: 1000, TestL: { Stim: 10, Resp: true }, TestR: { Stim: 10, Resp: true } }, - { Hz: 2000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 20, Resp: true } }, - { Hz: 3000, TestL: { Stim: 30, Resp: true }, TestR: { Stim: 40, Resp: true } }, - { Hz: 4000, TestL: { Stim: 40, Resp: true }, TestR: { Stim: 55, Resp: true } }, - { Hz: 6000, TestL: { Stim: 35, Resp: true }, TestR: { Stim: 40, Resp: true } }, - { Hz: 8000, TestL: { Stim: 20, Resp: true }, TestR: { Stim: 15, Resp: true } } - ] - }, - { - Name: "Patient B High Freq Hearing Loss", - Plot: - [ - { Hz: 500, TestL: { Stim: 10, Resp: true }, TestR: { Stim: 10, Resp: true } }, - { Hz: 1000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 10, Resp: true } }, - { Hz: 2000, TestL: { Stim: 10, Resp: true }, TestR: { Stim: 15, Resp: true } }, - { Hz: 3000, TestL: { Stim: 25, Resp: true }, TestR: { Stim: 20, Resp: true } }, - { Hz: 4000, TestL: { Stim: 35, Resp: true }, TestR: { Stim: 35, Resp: true } }, - { Hz: 6000, TestL: { Stim: 50, Resp: true }, TestR: { Stim: 55, Resp: true } }, - { Hz: 8000, TestL: { Stim: 80, Resp: true }, TestR: { Stim: 75, Resp: true } } - ] - }, - { - Name: "Patient C Unilateral Hearing Loss", - Plot: - [ - { Hz: 500, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 40, Resp: true } }, - { Hz: 1000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 50, Resp: true } }, - { Hz: 2000, TestL: { Stim: 20, Resp: true }, TestR: { Stim: 65, Resp: true } }, - { Hz: 3000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 70, Resp: true } }, - { Hz: 4000, TestL: { Stim: 20, Resp: true }, TestR: { Stim: 65, Resp: true } }, - { Hz: 6000, TestL: { Stim: 25, Resp: true }, TestR: { Stim: 60, Resp: true } }, - { Hz: 8000, TestL: { Stim: 20, Resp: true }, TestR: { Stim: 45, Resp: true } } - ] - }, - { - Name: "Patient D Normal Hearing", - Plot: - [ - { Hz: 500, TestL: { Stim: 5, Resp: true }, TestR: { Stim: 10, Resp: true } }, - { Hz: 1000, TestL: { Stim: 0, Resp: true }, TestR: { Stim: 5, Resp: true } }, - { Hz: 2000, TestL: { Stim: 5, Resp: true }, TestR: { Stim: 5, Resp: true } }, - { Hz: 3000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 10, Resp: true } }, - { Hz: 4000, TestL: { Stim: 15, Resp: true }, TestR: { Stim: 15, Resp: true } }, - { Hz: 6000, TestL: { Stim: 5, Resp: true }, TestR: { Stim: 10, Resp: true } }, - { Hz: 8000, TestL: { Stim: 0, Resp: true }, TestR: { Stim: 5, Resp: true } } - ] - } - ] -}, {Name:"Test", Data:0}); + } +}, {Name:"Test", Data:SettingsActual.Pick}); -/** @type {preact.Context} */ -export const Context = React.createContext([Initial, (_a)=>{}]); + +export const Context = React.createContext(/** @type {Store.Binding} */([Initial, (_a)=>{}])); /** @type {(props:{children:preact.ComponentChildren})=>preact.VNode} */ export const Provider =(props)=> @@ -251,55 +351,4 @@ export const Provider =(props)=> }; /** @type {()=>Store.Binding} */ -export const Consumer =()=> React.useContext(Context); - -/** @type {(inTest:Store.Test|undefined)=>Store.Grade} */ -export const Grade =(inTest)=> -{ - /** @type {Store.Grade} */ - const output = { Total:0, Done:0, Score:0 }; - - /** @type {(inGoal:number, inResult:number)=>number} */ - const Mapper =(inGoal, inResult)=> - { - const err = Math.abs(inGoal-inResult); - if(err == 0){ return 1; } - else if(err > 0 && err <= 5){ return 0.9; } - else if(err > 5 && err <= 10){ return 0.7; } - else if(err > 10 && err <= 15){ return 0.2; } - else{ return 0; } - } - - if(inTest) - { - for(let i=0; i 0) - { - output.Score = Math.floor((output.Score/output.Done) * 10000)/100; - } - - return output; -} \ No newline at end of file +export const Consumer =()=> React.useContext(Context); \ No newline at end of file diff --git a/src/tone.js b/js/tone.js similarity index 100% rename from src/tone.js rename to js/tone.js diff --git a/src/twind.js b/js/twind.js similarity index 96% rename from src/twind.js rename to js/twind.js index 6cbbdf2..adca149 100644 --- a/src/twind.js +++ b/js/twind.js @@ -2,7 +2,7 @@ import * as TW from "@twind/core@1.0.1"; import TWPreTail from "@twind/preset-tailwind@1.0.1"; import TWPreAuto from "@twind/preset-autoprefix@1.0.1"; -/** @type {TW.TwindConfig} */ +/** @type {TW.TwindUserConfig} */ export const Configure = { theme: { @@ -71,7 +71,7 @@ export const Configure = { } ], [ - 'text-shadow-lcd', {"text-shadow": "0px 2px 2px #00000055"} + 'text-shadow-lcd', {"text-shadow": "0px 1px 1px #00000055"} ], [ 'box-notch', "border-t(1 [#ffffff]) border-r(1 [#ffffff]) border-b(1 [#00000033]) border-l(1 [#00000033]) flex items-center justify-end gap-1 p-2" ], [ "box-buttons", "flex gap-1 items-center p-2 rounded-lg bg-gradient-to-b from-[#00000022] border-b(1 [#ffffff]) border-t(1 [#00000033])"] diff --git a/src/ui.js b/js/ui.js similarity index 50% rename from src/ui.js rename to js/ui.js index 24d43f2..19b36c1 100644 --- a/src/ui.js +++ b/js/ui.js @@ -3,7 +3,7 @@ import { html } from "htm"; import * as Store from "./store.js"; import * as Tone from "./tone.js"; -/** @typedef {({children}:{children?:preact.ComponentChildren})=>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} */ export function Button({children, icon, light, disabled, inactive, onClick, classes}) @@ -36,44 +36,65 @@ export function Button({children, icon, light, disabled, inactive, onClick, clas } /** @type {BasicElement} */ -export const Select =()=> +export const Header =()=> { const [State, Dispatch] = Store.Consumer(); - const grade = Store.Grade(State.Live.Test); + const grade = State.Live.Test?.Done || {Marks:0, Total:0, Score:0}; /** @type {(e:Event)=>void} */ const handleChange =(e)=> Dispatch({Name:"Test", Data:parseInt(/** @type {HTMLSelectElement}*/(e.target).value)}); return html` -
-
-
Select Test
-
- ${State.Test.map((t, i)=>html``)}
-
-
-
Progress
-
-
Complete: ${grade.Done} of ${grade.Total}
-
-
-
-
Accuracy: ${grade.Score}%
+
+

Patient Error:

+ <${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
-
-
Display
-
- <${Button} light=${State.Show.Cursor} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"ShowCursor", Data:!State.Show.Cursor})}>Cursor - <${Button} light=${State.Show.Answer} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"ShowAnswer", Data:!State.Show.Answer})}>Answer + +
+
+
Complete: ${grade.Marks} of ${grade.Total}
+
+
+
+
Accuracy: ${grade.Score}%
+ <${Button} disabled=${grade.Marks == 0} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"Kill", Data:0})}>Start Over
`; } +/** @type {BasicElement} */ +export const Display =()=> +{ + const [State, Dispatch] = Store.Consumer(); + return html` +
+
+
+ <${Button} light=${State.Show.Cursor} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"ShowCursor", Data:!State.Show.Cursor})}>Cursor + <${Button} light=${State.Show.Answer} classes="flex-1 text-xs" onClick=${()=>Dispatch({Name:"ShowAnswer", Data:!State.Show.Answer})}>Answer +
+
+
+ `; +}; + /** @type {BasicElement} */ export const Controls =()=> { @@ -93,7 +114,26 @@ export const Controls =()=> 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)} + const audible = State.Stim.Value >= testMark.Stim; + + 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 errorSampled = Math.random() < errorScaled; + 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); timer = setTimeout(handler, 800 + Math.random()*1300); } } @@ -104,130 +144,134 @@ export const Controls =()=> const classTitle = "flex-1 text-sm" return html` -
-
-
Channel
-
- <${Button} inactive=${State.Chan.Value == 0} light=${State.Chan.Value == 0} classes="flex-1" onClick=${()=>Dispatch({Name:"Chan", Data:-1})}>Left - <${Button} inactive=${State.Chan.Value == 1} light=${State.Chan.Value == 1} classes="flex-1" onClick=${()=>Dispatch({Name:"Chan", Data:1})}>Right -
-
-
-
Frequency
-
-
${Store.ColumnMapping[State.Freq.Value][0]} Hz
- <${Button} disabled=${State.Freq.Value == State.Freq.Min} onClick=${()=>Dispatch({Name:"Freq", Data:-1})}> - - <${Glyph.Minus}/> - - - <${Button} disabled=${State.Freq.Value == State.Freq.Max} onClick=${()=>Dispatch({Name:"Freq", Data:1})}> - - <${Glyph.Plus}/> - - -
-
-
-
Stimulus
-
-
${State.Stim.Value} dbHL
- <${Button} disabled=${State.Stim.Value == State.Stim.Min} onClick=${()=>Dispatch({Name:"Stim", Data:-1})}> - - <${Glyph.Minus}/> - - - <${Button} disabled=${State.Stim.Value == State.Stim.Max} onClick=${()=>Dispatch({Name:"Stim", Data:1})}> - - <${Glyph.Plus}/> - - -
-
-
- - - - - - - ${playGet == 2 && html``} - - - - - - - - - - - - - - - - - - - - -
-
- <${Button} - classes="w-full flex-1 self-center" - onClick=${()=>playSet(1)} - disabled=${playGet==1} - icon=${html` - - `} - > - Present Tone - -
- <${Button} onClick=${()=>{pulsedSet(true )}} light=${pulsedGet } inactive${pulsedGet } classes="flex-1 text(center xs)">Pulsed - <${Button} onClick=${()=>{pulsedSet(false)}} light=${!pulsedGet} inactive${!pulsedGet} classes="flex-1 text(center xs)">Continuous -
+
+
+
+
+ <${Button} inactive=${State.Chan.Value == 0} light=${State.Chan.Value == 0} classes="flex-1" onClick=${()=>Dispatch({Name:"Chan", Data:-1})}>Left + <${Button} inactive=${State.Chan.Value == 1} light=${State.Chan.Value == 1} classes="flex-1" onClick=${()=>Dispatch({Name:"Chan", Data:1})}>Right
+
+
+
${Store.ColumnMapping[State.Freq.Value][0]} Hz
+ <${Button} disabled=${State.Freq.Value == State.Freq.Min} onClick=${()=>Dispatch({Name:"Freq", Data:-1})}> + + <${Glyph.Minus}/> + + + <${Button} disabled=${State.Freq.Value == State.Freq.Max} onClick=${()=>Dispatch({Name:"Freq", Data:1})}> + + <${Glyph.Plus}/> + + +
+
+
+
+
${State.Stim.Value} dbHL
+ <${Button} disabled=${State.Stim.Value == State.Stim.Min} onClick=${()=>Dispatch({Name:"Stim", Data:-1})}> + + <${Glyph.Minus}/> + + + <${Button} disabled=${State.Stim.Value == State.Stim.Max} onClick=${()=>Dispatch({Name:"Stim", Data:1})}> + + <${Glyph.Plus}/> + + +
+
+
-
-
Threshold
-
- <${Button} - onClick=${()=>Dispatch({Name:"Mark", Data:true })} - classes="text-md w-full" - icon=${html` + +
+
+
+
+ <${Button} + classes="w-full flex-1 self-center" + onClick=${()=>playSet(1)} + disabled=${playGet==1} + icon=${html` + + `} + > + Present Tone + +
+ <${Button} onClick=${()=>{pulsedSet(true )}} light=${pulsedGet } inactive${pulsedGet } classes="flex-1 text(center xs)">Pulsed + <${Button} onClick=${()=>{pulsedSet(false)}} light=${!pulsedGet} inactive${!pulsedGet} classes="flex-1 text(center xs)">Continuous +
+
+
+ + + + + + + ${playGet == 2 && html``} + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ <${Button} + onClick=${()=>Dispatch({Name:"Mark", Data:true })} + classes="text-md w-full" + icon=${html` + + <${State.Chan.Value ? Glyph.O : Glyph.X}/> + `} + > + Accept + + <${Button} + onClick=${()=>Dispatch({Name:"Mark", Data:false})} + classes="text-sm w-full" + icon=${html` + + <${State.Chan.Value ? Glyph.O : Glyph.X}> + <${Glyph.Arrow}/> + + `} + > + No Response + + <${Button} + icon=${html` - <${State.Chan.Value ? Glyph.O : Glyph.X}/> - `} - > - Accept - - <${Button} - onClick=${()=>Dispatch({Name:"Mark", Data:false})} - classes="text-sm w-full" - icon=${html` - - <${State.Chan.Value ? Glyph.O : Glyph.X}> - <${Glyph.Arrow}/> - - `} - > - No Response - - <${Button} - icon=${html` - - <${Glyph.Null}/> - - `} - onClick=${()=>Dispatch({Name:"Mark", Data:null })} - classes="text-sm w-full" - disabled=${State.Live.Mark == undefined} - > - Clear - + <${Glyph.Null}/> + + `} + onClick=${()=>Dispatch({Name:"Mark", Data:null })} + classes="text-sm w-full" + disabled=${State.Live.Mark == undefined} + > + Clear + +
@@ -274,6 +318,7 @@ export const Audiogram =()=> }`; }; + /** @type {BasicElement} */ export function Chart({children}) { @@ -299,8 +344,8 @@ export function Chart({children}) ); } return html` -
-
+
+
Frequency in Hz @@ -308,7 +353,7 @@ export function Chart({children})
- + ${ rules }
${ children } diff --git a/store.d.ts b/ts/store.d.ts similarity index 77% rename from store.d.ts rename to ts/store.d.ts index 95a60b5..cdde39f 100644 --- a/store.d.ts +++ b/ts/store.d.ts @@ -12,7 +12,11 @@ declare namespace Store { UserR?: TestFrequencySample; }; - type Test = { Name: string; Plot: Array }; + type Test = { + Name : string; + Done?: Grade; + Plot : Array + }; type Context = { Test?: Test; @@ -20,25 +24,38 @@ declare namespace Store { Mark?: TestFrequencySample; }; - type State = { + type StatePartSimple = + { Chan: Range; Freq: Range; Stim: Range; + Errs: number; + Pick: number; + Show: + { + Cursor:boolean, + Answer:boolean + } + }; + type StatePartComplex = + { Live: Context; Draw: DrawChart; - Show: {Cursor:boolean, Answer:boolean} - TestIndex: number; Test: Array; }; + type State = StatePartSimple & StatePartComplex; + type ActionMark = { Name: "Mark"; Data: boolean | null }; type ActionTest = { Name: "Test"; Data: number }; type ActionChan = { Name: "Chan"; Data: number }; type ActionFreq = { Name: "Freq"; Data: number }; type ActionStim = { Name: "Stim"; Data: number }; + type ActionErrs = { Name: "Errs"; Data: number }; + type ActionKill = { Name: "Kill"; Data: number }; type ActionShowCursor = {Name: "ShowCursor", Data:boolean}; type ActionShowAnswer = {Name: "ShowAnswer", Data:boolean}; - type Action = ActionMark | ActionTest | ActionChan | ActionFreq | ActionStim | ActionShowCursor | ActionShowAnswer; + type Action = ActionMark | ActionTest | ActionChan | ActionFreq | ActionStim | ActionErrs | ActionKill | ActionShowCursor | ActionShowAnswer; type Reducer = (inState: State, inAction: Action) => State; type ContextUpdater = (inState: State) => boolean; @@ -55,7 +72,7 @@ declare namespace Store { type Grade = { Total:number, - Done:number, + Marks:number, Score:number }; } \ No newline at end of file diff --git a/store.test.ts b/ts/store.test.ts similarity index 98% rename from store.test.ts rename to ts/store.test.ts index 39c1ccd..7c29953 100644 --- a/store.test.ts +++ b/ts/store.test.ts @@ -1,5 +1,5 @@ import { assertEquals } from "https://deno.land/std@0.166.0/testing/asserts.ts"; -import { Reducer, ColumnMapping, Initial } from "./src/store.js"; +import { Reducer, ColumnMapping, Initial } from "../js/store.js"; let state:Store.State = { Chan: { Min:0, Max:1, Value:0, Step:1 },