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`
-