feature/layout-updates #1
70
src/app.js
70
src/app.js
@ -13,76 +13,12 @@ ShadowDOM.append(ShadowDiv);
|
|||||||
|
|
||||||
TW.Init(ShadowCSS, ShadowDiv);
|
TW.Init(ShadowCSS, ShadowDiv);
|
||||||
|
|
||||||
const Controls =()=>
|
|
||||||
{
|
|
||||||
const [State, Dispatch] = Store.Consumer();
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="flex">
|
|
||||||
<div>Channel</div>
|
|
||||||
<div>${State.Chan.Value}</div>
|
|
||||||
<${UI.Button} light=${State.Chan.Value == 0} inactive=${State.Chan.Value == 0} onClick=${()=>Dispatch({Name:"Chan", Data:-1})}>Left<//>
|
|
||||||
<${UI.Button} light=${State.Chan.Value == 1} inactive=${State.Chan.Value == 1} onClick=${()=>Dispatch({Name:"Chan", Data:1})}>Right<//>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<div>Frequency</div>
|
|
||||||
<div>${Store.ColumnMapping[State.Freq.Value][0]}</div>
|
|
||||||
<${UI.Button} disabled=${State.Freq.Value == State.Freq.Min} onClick=${()=>Dispatch({Name:"Freq", Data:-1})}>-<//>
|
|
||||||
<${UI.Button} disabled=${State.Freq.Value == State.Freq.Max} onClick=${()=>Dispatch({Name:"Freq", Data:1})}>+<//>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<div>Stimulus</div>
|
|
||||||
<div>${State.Stim.Value}</div>
|
|
||||||
<${UI.Button} disabled=${State.Stim.Value == State.Stim.Min} onClick=${()=>Dispatch({Name:"Stim", Data:-1})}>-<//>
|
|
||||||
<${UI.Button} disabled=${State.Stim.Value == State.Stim.Max} onClick=${()=>Dispatch({Name:"Stim", Data:1})}>+<//>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<div>Mark</div>
|
|
||||||
<${UI.Button} onClick=${()=>Dispatch({Name:"Mark", Data:true })}>Response<//>
|
|
||||||
<${UI.Button} onClick=${()=>Dispatch({Name:"Mark", Data:false})}>No Response<//>
|
|
||||||
<${UI.Button} onClick=${()=>Dispatch({Name:"Mark", Data:null })} disabled=${State.Live.Mark == undefined}>Clear<//>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Audiogram =()=>
|
|
||||||
{
|
|
||||||
const [State] = Store.Consumer();
|
|
||||||
|
|
||||||
const testMarksL = State.Draw.TestL.Points.map(p=>html`<${UI.Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${false}/>`);
|
|
||||||
const userMarksL = State.Draw.UserL.Points.map(p=>html`<${UI.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`<${UI.Mark} x=${p.X} y=${p.Y} response=${p.Mark?.Resp} right=${true} />`);
|
|
||||||
const userMarksR = State.Draw.UserR.Points.map(p=>html`<${UI.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`<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 testLinesR = State.Draw.TestR.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 userLinesR = State.Draw.UserR.Paths.map( p=>html`<line class="opacity-60" x1=${p.Head.X} y1=${p.Head.Y} x2=${p.Tail.X} y2=${p.Tail.Y} />`);
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<svg class="absolute top-0 w-full h-full overflow-visible stroke(blue-700 bold draw) opacity-50">${testMarksL}${testLinesL}</svg>
|
|
||||||
<svg class="absolute top-0 w-full h-full overflow-visible stroke(red-700 bold draw) opacity-50">${testMarksR}${testLinesR}</svg>
|
|
||||||
<svg class="absolute top-0 w-full h-full overflow-visible stroke(blue-700 2 draw)">${userMarksL}${userLinesL}</svg>
|
|
||||||
<svg class="absolute top-0 w-full h-full overflow-visible stroke(red-700 2 draw)">${userMarksR}${userLinesR}</svg>
|
|
||||||
<svg class="absolute top-0 w-full h-full overflow-visible transition-all duration-500" style=${{top:State.Draw.Cross?.Y, left:State.Draw.Cross?.X}}>
|
|
||||||
<ellipse cx="0" cy="0" rx="8" ry="30" fill="url(#glow)"></ellipse>
|
|
||||||
<ellipse cx="0" cy="0" rx="30" ry="8" fill="url(#glow)"></ellipse>
|
|
||||||
<defs>
|
|
||||||
<radialGradient id="glow">
|
|
||||||
<stop stop-color=${State.Chan.Value ? "red" : "blue"} stop-opacity="0.6" offset="0.0"></stop>
|
|
||||||
<stop stop-color=${State.Chan.Value ? "red" : "blue"} stop-opacity="0.3" offset="0.2"></stop>
|
|
||||||
<stop stop-color=${State.Chan.Value ? "red" : "blue"} stop-opacity="0.0" offset="1.0"></stop>
|
|
||||||
</radialGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
React.render(html`
|
React.render(html`
|
||||||
<${Store.Provider}>
|
<${Store.Provider}>
|
||||||
<${Controls}/>
|
<${UI.Select}/>
|
||||||
|
<${UI.Controls}/>
|
||||||
<${UI.Chart}>
|
<${UI.Chart}>
|
||||||
<${Audiogram}/>
|
<${UI.Audiogram}/>
|
||||||
<//>
|
<//>
|
||||||
<//>
|
<//>
|
||||||
`, ShadowDiv);
|
`, ShadowDiv);
|
38
src/store.js
38
src/store.js
@ -108,16 +108,20 @@ export function Reducer(inState, inAction)
|
|||||||
|
|
||||||
if(Name == "Test")
|
if(Name == "Test")
|
||||||
{
|
{
|
||||||
const test = clone.Tests[Data];
|
const test = clone.Test[Data];
|
||||||
clone.Live = Reselect(clone, test);
|
if(test)
|
||||||
clone.Draw =
|
|
||||||
{
|
{
|
||||||
Cross: Reposition(clone),
|
clone.TestIndex = Data;
|
||||||
UserL: Redraw(test, 0, clone.Stim, true ),
|
clone.Live = Reselect(clone, test);
|
||||||
UserR: Redraw(test, 1, clone.Stim, true ),
|
clone.Draw =
|
||||||
TestL: Redraw(test, 0, clone.Stim, false),
|
{
|
||||||
TestR: Redraw(test, 1, clone.Stim, false)
|
Cross: Reposition(clone),
|
||||||
};
|
UserL: Redraw(test, 0, clone.Stim, true ),
|
||||||
|
UserR: Redraw(test, 1, clone.Stim, true ),
|
||||||
|
TestL: Redraw(test, 0, clone.Stim, false),
|
||||||
|
TestR: Redraw(test, 1, clone.Stim, false)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (Name == "Mark")
|
else if (Name == "Mark")
|
||||||
{
|
{
|
||||||
@ -165,7 +169,8 @@ export const Initial = Reducer(
|
|||||||
TestL:{Points:[], Paths:[]},
|
TestL:{Points:[], Paths:[]},
|
||||||
TestR:{Points:[], Paths:[]}
|
TestR:{Points:[], Paths:[]}
|
||||||
},
|
},
|
||||||
Tests: [
|
TestIndex: 0,
|
||||||
|
Test: [
|
||||||
{
|
{
|
||||||
Name: "Patient A Asymmetric Notch",
|
Name: "Patient A Asymmetric Notch",
|
||||||
Plot:
|
Plot:
|
||||||
@ -178,6 +183,19 @@ export const Initial = Reducer(
|
|||||||
{ Hz: 6000, TestL: { Stim: 50, Resp: true }, TestR: { Stim: 55, Resp: true } },
|
{ Hz: 6000, TestL: { Stim: 50, Resp: true }, TestR: { Stim: 55, Resp: true } },
|
||||||
{ Hz: 8000, TestL: { Stim: 50, Resp: true }, TestR: { Stim: 55, Resp: true } }
|
{ Hz: 8000, TestL: { Stim: 50, Resp: true }, TestR: { Stim: 55, Resp: true } }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Patient B Asymmetric Notch",
|
||||||
|
Plot:
|
||||||
|
[
|
||||||
|
{ Hz: 500, TestL: { Stim: 50, Resp: true }, TestR: { Stim: 70, Resp: true } },
|
||||||
|
{ Hz: 1000, TestL: { Stim: 30, Resp: true }, TestR: { Stim: 25, Resp: true } },
|
||||||
|
{ Hz: 2000, TestL: { Stim: 30, Resp: true }, TestR: { Stim: 25, Resp: true } },
|
||||||
|
{ Hz: 3000, TestL: { Stim: 30, Resp: true }, TestR: { Stim: 25, Resp: true } },
|
||||||
|
{ Hz: 4000, TestL: { Stim: 30, Resp: true }, TestR: { Stim: 25, Resp: true } },
|
||||||
|
{ Hz: 6000, TestL: { Stim: 30, Resp: true }, TestR: { Stim: 25, Resp: true } },
|
||||||
|
{ Hz: 8000, TestL: { Stim: 30, Resp: true }, TestR: { Stim: 25, Resp: true } }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {Name:"Test", Data:0});
|
}, {Name:"Test", Data:0});
|
||||||
|
86
src/ui.js
86
src/ui.js
@ -34,6 +34,89 @@ export function Button({children, icon, light, disabled, inactive, onClick})
|
|||||||
</button>`;
|
</button>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @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`
|
||||||
|
<div class="font-sans">
|
||||||
|
<label for="#test-select" class="inline-block">Select Test:</label>
|
||||||
|
<select id="test-select" class="px-2 py-2 rounded border(1 slate-200) inline-block" value=${State.TestIndex} onChange=${handleChange}>
|
||||||
|
${State.Test.map((t, i)=>html`<option value=${i}>${t.Name}</option>`)}
|
||||||
|
</select>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {BasicElement} */
|
||||||
|
export const Controls =()=>
|
||||||
|
{
|
||||||
|
const [State, Dispatch] = Store.Consumer();
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="flex">
|
||||||
|
<div>Channel</div>
|
||||||
|
<div>${State.Chan.Value}</div>
|
||||||
|
<${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<//>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div>Frequency</div>
|
||||||
|
<div>${Store.ColumnMapping[State.Freq.Value][0]}</div>
|
||||||
|
<${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})}>+<//>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div>Stimulus</div>
|
||||||
|
<div>${State.Stim.Value}</div>
|
||||||
|
<${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})}>+<//>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div>Mark</div>
|
||||||
|
<${Button} onClick=${()=>Dispatch({Name:"Mark", Data:true })}>Response<//>
|
||||||
|
<${Button} onClick=${()=>Dispatch({Name:"Mark", Data:false})}>No Response<//>
|
||||||
|
<${Button} onClick=${()=>Dispatch({Name:"Mark", Data:null })} disabled=${State.Live.Mark == undefined}>Clear<//>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @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`<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 testLinesR = State.Draw.TestR.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 userLinesR = State.Draw.UserR.Paths.map( p=>html`<line class="opacity-60" x1=${p.Head.X} y1=${p.Head.Y} x2=${p.Tail.X} y2=${p.Tail.Y} />`);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<svg class="absolute top-0 w-full h-full overflow-visible stroke(blue-700 bold draw) opacity-50">${testMarksL}${testLinesL}</svg>
|
||||||
|
<svg class="absolute top-0 w-full h-full overflow-visible stroke(red-700 bold draw) opacity-50">${testMarksR}${testLinesR}</svg>
|
||||||
|
<svg class="absolute top-0 w-full h-full overflow-visible stroke(blue-700 2 draw)">${userMarksL}${userLinesL}</svg>
|
||||||
|
<svg class="absolute top-0 w-full h-full overflow-visible stroke(red-700 2 draw)">${userMarksR}${userLinesR}</svg>
|
||||||
|
<svg class="absolute top-0 w-1 h-1 overflow-visible transition-all duration-500" style=${{top:State.Draw.Cross?.Y, left:State.Draw.Cross?.X}}>
|
||||||
|
<ellipse cx="0" cy="0" rx="8" ry="30" fill="url(#glow)"></ellipse>
|
||||||
|
<ellipse cx="0" cy="0" rx="30" ry="8" fill="url(#glow)"></ellipse>
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="glow">
|
||||||
|
<stop stop-color=${State.Chan.Value ? "red" : "blue"} stop-opacity="0.6" offset="0.0"></stop>
|
||||||
|
<stop stop-color=${State.Chan.Value ? "red" : "blue"} stop-opacity="0.3" offset="0.2"></stop>
|
||||||
|
<stop stop-color=${State.Chan.Value ? "red" : "blue"} stop-opacity="0.0" offset="1.0"></stop>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
/** @type {BasicElement} */
|
/** @type {BasicElement} */
|
||||||
export function Chart({children})
|
export function Chart({children})
|
||||||
{
|
{
|
||||||
@ -78,7 +161,6 @@ export function Chart({children})
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** @type {Record<string, BasicElement>} */
|
/** @type {Record<string, BasicElement>} */
|
||||||
const Glyph = {
|
const Glyph = {
|
||||||
Arrow:()=> html`
|
Arrow:()=> html`
|
||||||
@ -105,4 +187,4 @@ export const Mark =({right, response, x, y, classes})=>
|
|||||||
${ !response && html`<${Glyph.Arrow}/>` }
|
${ !response && html`<${Glyph.Arrow}/>` }
|
||||||
<//>
|
<//>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
}
|
};
|
3
store.d.ts
vendored
3
store.d.ts
vendored
@ -26,7 +26,8 @@ declare namespace Store {
|
|||||||
Stim: Range;
|
Stim: Range;
|
||||||
Live: Context;
|
Live: Context;
|
||||||
Draw: DrawChart;
|
Draw: DrawChart;
|
||||||
Tests: Array<Test>;
|
TestIndex: number;
|
||||||
|
Test: Array<Test>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ActionMark = { Name: "Mark"; Data: boolean | null };
|
type ActionMark = { Name: "Mark"; Data: boolean | null };
|
||||||
|
@ -18,7 +18,7 @@ let state:Store.State = {
|
|||||||
TestL:{Points:[], Paths:[]},
|
TestL:{Points:[], Paths:[]},
|
||||||
TestR:{Points:[], Paths:[]}
|
TestR:{Points:[], Paths:[]}
|
||||||
},
|
},
|
||||||
Tests: [
|
Test: [
|
||||||
{
|
{
|
||||||
Name: "Patient A Asymmetric Notch",
|
Name: "Patient A Asymmetric Notch",
|
||||||
Plot:
|
Plot:
|
||||||
@ -42,8 +42,8 @@ Deno.test("Initialize", async(t)=>
|
|||||||
|
|
||||||
await t.step("A test exists with 500 and 1k hz plots", ()=>
|
await t.step("A test exists with 500 and 1k hz plots", ()=>
|
||||||
{
|
{
|
||||||
assertEquals(state.Tests.length > 0, true);
|
assertEquals(state.Test.length > 0, true);
|
||||||
const test = state.Tests[0];
|
const test = state.Test[0];
|
||||||
assertEquals(test.Plot.length > 1, true);
|
assertEquals(test.Plot.length > 1, true);
|
||||||
assertEquals(test.Plot[0].Hz, 500);
|
assertEquals(test.Plot[0].Hz, 500);
|
||||||
assertEquals(test.Plot[1].Hz, 1000);
|
assertEquals(test.Plot[1].Hz, 1000);
|
||||||
@ -67,7 +67,7 @@ Deno.test("Initialize", async(t)=>
|
|||||||
|
|
||||||
await t.step("Live context values are correct", ()=>
|
await t.step("Live context values are correct", ()=>
|
||||||
{
|
{
|
||||||
assertEquals(state.Live.Test, state.Tests[0]);
|
assertEquals(state.Live.Test, state.Test[0]);
|
||||||
assertEquals(state.Live.Freq?.Hz, ColumnMapping[state.Freq.Value][0]);
|
assertEquals(state.Live.Freq?.Hz, ColumnMapping[state.Freq.Value][0]);
|
||||||
assertEquals(state.Live.Mark, undefined, "(User) Mark is undefined");
|
assertEquals(state.Live.Mark, undefined, "(User) Mark is undefined");
|
||||||
});
|
});
|
||||||
@ -120,7 +120,7 @@ Deno.test("Make Marks", async(t)=>
|
|||||||
|
|
||||||
await t.step("Live context values are correct", ()=>
|
await t.step("Live context values are correct", ()=>
|
||||||
{
|
{
|
||||||
assertEquals(state.Live.Test, state.Tests[0]);
|
assertEquals(state.Live.Test, state.Test[0]);
|
||||||
assertEquals(state.Live.Freq?.Hz, ColumnMapping[state.Freq.Value][0]);
|
assertEquals(state.Live.Freq?.Hz, ColumnMapping[state.Freq.Value][0]);
|
||||||
assertEquals(state.Live.Mark?.Stim, state.Stim.Value);
|
assertEquals(state.Live.Mark?.Stim, state.Stim.Value);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user