Skip to content

Instantly share code, notes, and snippets.

@BHWD
Created April 9, 2019 15:04
Show Gist options
  • Save BHWD/3cf9a326e8562e3bc76d702448a91995 to your computer and use it in GitHub Desktop.
Save BHWD/3cf9a326e8562e3bc76d702448a91995 to your computer and use it in GitHub Desktop.
import React, { Component } from 'react';
import { Document, Page } from 'react-pdf';
import './App.css';
import './font-awesome-4.7.0/css/font-awesome.min.css';
//===================================================================================
//===================================================================================
function NavLink(props) { // top menu nav links to modules
//===================================================================================
//===================================================================================
return (
<div className={"nav-link"+(props.obj.state?" hi":"")} onClick={props.onClick}>
<div className="nav-link-title">{props.obj.label}</div>
<div className="nav-link-subtitle" dangerouslySetInnerHTML={{__html: props.obj.subtitle}} />
</div>
);
}
//===================================================================================
//===================================================================================
class ChapterLink extends Component { // sidebar menu nav links to chapters
//===================================================================================
//===================================================================================
// constructor(props) {
// super(props);
// }
renderSectionLink(s) {
return (
<SectionLink
key={s}
obj={this.props.obj.sections[s]}
onClick={()=>{ this.props.onClickSection(s) }}
/>
);
}
render(){
if (this.props.obj.sections.length>0)
{
if (this.props.obj.state===1)
{
var sections=[];
for (var s=0 ; s<this.props.obj.sections.length ; s++)
{
sections.push(this.renderSectionLink(s));
}
return (
<li className={"chapter hi"} >
{this.props.obj.title}
<i className="fa fa-angle-double-down"/>
<ol>
{sections}
</ol>
</li>
);
}
else
{
return (
<li className={"chapter"} onClick={this.props.onClick}>
{this.props.obj.title}
<i className="fa fa-angle-double-down"/>
</li>
);
}
}
else
{
return (
<li className={"chapter"+(this.props.obj.state?" hi":"")} onClick={this.props.onClick}>
{this.props.obj.title}
</li>
);
}
}
}
//===================================================================================
//===================================================================================
function SectionLink(props) { // sidebar menu nav links to chapter sections
//===================================================================================
//===================================================================================
return (
<li className={"section"+(props.obj.state?" hi":"")} onClick={props.onClick}>
{props.obj.title}
</li>
);
}
//===================================================================================
//===================================================================================
function TestLink(props) { // sidebar menu nav leink to self assessment
//===================================================================================
//===================================================================================
return (
<div className={"test"+(props.obj.state?" hi":"")} onClick={props.onClick}>
<i className="fa fa-edit"/>
{props.obj.title}
</div>
);
}
//===================================================================================
//===================================================================================
function LNav(props) { //
//===================================================================================
//===================================================================================
return (
<i
className={props.cls}
onClick={()=>{props.onClickDot(props.c,props.s)}}
/>
);
}
//===================================================================================
//===================================================================================
function Answer(props) {
//===================================================================================
//===================================================================================
return (
<div className="answer">
<label>
<input type='radio'
checked={ (props.checked===props.a )}
name={'Q'+props.q}
onChange={props.onAnswer}
/>
<span dangerouslySetInnerHTML={{__html: props.obj.answer}}/>
</label>
</div>
);
}
//===================================================================================
//===================================================================================
class Question extends Component {
//===================================================================================
//===================================================================================
renderAnswer(q,a) {
return (
<Answer
a={a+1}
q={q}
key={a+1}
checked={this.props.obj.response}
obj={this.props.obj.answers[a]}
correct={this.props.obj.correct}
onAnswer={()=> {
this.props.onAnswer(a+1);
}}
/>);
}
render(){
var answers=[];
for (var a=0;a<this.props.obj.answers.length;a++)
{
answers.push(this.renderAnswer(this.props.q, a));
}
return (
<div className="question" >
<span className="number">Q{this.props.q}:</span><span className="text" dangerouslySetInnerHTML={{__html: this.props.obj.question}}/>
<div>{answers}</div>
<div className={'response'+(this.props.obj.correct?' correct':'')}>
<div className='correct'><i className='fa fa-check-circle'/><span className="title">CORRECT!</span></div>
<div className='incorrect'><i className='fa fa-times-circle'/>
<span className="title">INCORRECT!</span>
<span className='hint' onClick={this.props.fredirect}>
{"Please re-read section: "+this.props.hint_title}
</span>
</div>
</div>
</div>
);
}
}
//===================================================================================
//===================================================================================
class App extends Component {
//===================================================================================
//===================================================================================
onDocumentLoadSuccess = (document) => {
const {numPages} =document;
this.setState({ numPages ,pageNumber:1});
this.pdfResize(100);
}
changePage = offset => {
var newpage=this.state.pageNumber + offset;
if (newpage<1) return;
if (newpage>this.state.numPages) return;
this.setState(prevState => ({ pageNumber: newpage, }));
}
previousPage = () => this.changePage(-1);
nextPage = () => this.changePage(1);
constructor(props) {
super(props);
this.state={
config:{
nlinks:[],
},
numPages: null,
pageNumber: 1,
popup: null,
pdf:null,
homelhs:"",
homerhs:"",
currentNlink:0,
currentClink:0,
};
const json = require('../public/config.json');
for (var n=0 ; n<json.nlinks.length ; n++)
{
json.nlinks[n].state = (n===0?1:0); // create state field dynamically
json.nlinks[n].currentChapter = 0; // create field dynamically
if (json.nlinks[n].chapters)
{
if (!json.nlinks[n].test) json.nlinks[n].test={state:0,submitted:0,title:"dummy",questions:[]}; // create dummy test dynamically
for (var q=0 ; q<json.nlinks[n].test.questions.length ; q++)
{
json.nlinks[n].test.questions[q].response=0;
json.nlinks[n].test.questions[q].correct=false;
}
for(var c=0 ; c<json.nlinks[n].chapters.length ; c++)
{
json.nlinks[n].chapters[c].state = (c===0?1:0); // create state field dynamically
if (json.nlinks[n].chapters[c].sections)
{
json.nlinks[n].chapters[c].currentSection=0;
for (var s=0 ; s<json.nlinks[n].chapters[c].sections.length ; s++)
{
json.nlinks[n].chapters[c].sections[s].state = (s===0?1:0); // create state field dynamically
}
}
else
{
json.nlinks[n].chapters[c].sections=[];
}
}
}
else
{
json.nlinks[n].chapters=[];
}
}
this.setState({config: json,});
this.handleSubmit=this.handleSubmit.bind(this);
// this.handleChange=this.handleChange.bind(this);
}
renderNavLink(n) {
return (
<NavLink
key={n}
obj={this.state.config.nlinks[n]}
onClick={ ()=>
{
this.goto(n,0,0);
}
}
/>
);
}
renderChapterLink(n,c) {
return (
<ChapterLink
key={c}
onClickSection={ (s)=>
{
this.goto(this.state.currentNlink,c,s);
}}
obj={this.state.config.nlinks[n].chapters[c]}
onClick={ ()=>
{
// console.log("clicked ChapterLink "+n+","+c);
this.goto(this.state.currentNlink,c,0);
} }
/>
);
}
renderChapterDot(n,c,s) {
var cls="fa fa-circle-o";
if (c<this.state.config.nlinks[n].chapters.length)
{
if (this.state.config.nlinks[n].chapters[c].state===1)
{
if (this.state.config.nlinks[n].chapters[c].sections.length>0)
{
if (this.state.config.nlinks[n].chapters[c].sections[s].state===1)
{
cls="fa fa-circle";
}
}
else
{
cls="fa fa-circle";
}
}
}
else if (this.state.config.nlinks[n].test && this.state.config.nlinks[n].test.state===1)
{
cls="fa fa-circle";
}
return (
<LNav
key={c*100 + s}
c={c}
s={s}
cls={cls}
onClickDot={(p_chapter, p_section)=>{
// console.log("lower click chapter "+p_chapter+" section "+p_section);
this.goto(this.state.currentNlink,p_chapter, p_section);
}}
/>
);
}
PDFLink= (e)=> { // Open PDF with `shell` method
console.log("Clicked PDF: ");
var attrs=e.currentTarget.attributes;
const shell = window.require('electron').shell;
const remote = window.require('electron').remote;
const appPath = remote.app.getAppPath();
console.log('appPath: ', appPath);
for (var a=0;a<attrs.length;a++)
{
console.log(attrs[a].name+"="+attrs[a].value);
switch (attrs[a].name)
{
case 'data-pdf':
//console.log(app.getAppPath());
shell.openItem(appPath+'\\public\\pages\\test1.pdf');
break;
default:
break;
}
}
}
ExternalLink= (e)=> { // Open Web Link In New Electron Window
console.log("Clicked External Link");
var attrs=e.currentTarget.attributes;
const remote = window.require('electron').remote;
const BrowserWindow = remote.BrowserWindow;
const path = require('path');
for (var a=0;a<attrs.length;a++)
{
console.log(attrs[a].name+"="+attrs[a].value);
switch (attrs[a].name)
{
case 'data-url':
var win = new BrowserWindow({ title: 'Web Viewer', autoHideMenuBar: true, width: 1024, height: 768 });
win.loadURL(attrs[a].value);
break;
default:
break;
}
}
}
renderTestLink(n) {
return (
<TestLink
obj={this.state.config.nlinks[n].test}
onClick={ ()=>
{
console.log("clicked TestLink "+n);
this.goto(this.state.currentNlink,this.state.config.nlinks[n].chapters.length,0);
}}
/>
);
}
renderQuestion(n,q) {
var c=this.state.config.nlinks[n].test.questions[q].hint-1;
var s=0;
if (this.state.config.nlinks[n].test.questions[q].hint_section) s=this.state.config.nlinks[n].test.questions[q].hint_section-1;
if (s>=this.state.config.nlinks[n].chapters[c].sections.length) s=0;
var t=this.state.config.nlinks[n].chapters[c].title;
if (this.state.config.nlinks[n].chapters[c].sections.length>0)
{
t+=": "+this.state.config.nlinks[n].chapters[c].sections[s].title;
}
return (
<Question
key={q}
n={n}
q={q+1}
obj={this.state.config.nlinks[n].test.questions[q]}
correct={this.state.config.nlinks[n].test.questions[q].answer}
hint_title={t}
fredirect={()=>
{
this.goto(this.state.currentNlink, c, s);
}}
onAnswer={ (a)=>
{
//console.log("clicked Answer for question "+q+" answer "+a);
const cf = JSON.parse(JSON.stringify(this.state.config));
cf.nlinks[n].test.questions[q].response=a;
cf.nlinks[n].test.questions[q].response=a;
cf.nlinks[n].test.questions[q].correct=(a===this.state.config.nlinks[n].test.questions[q].answer)
this.setState({
config: cf,
});
}}
/>
);
}
popitClicked= (e)=> {
console.log("Clicked popit: ");
var attrs=e.currentTarget.attributes;
for (var a=0;a<attrs.length;a++)
{
console.log(attrs[a].name+"="+attrs[a].value);
switch (attrs[a].name)
{
case 'data-popup':
this.fetchHTML(attrs[a].value,'popup');
break;
case 'data-pdf':
this.setState({pdf: attrs[a].value})
break;
default:
break;
}
}
}
linkitClicked= (e)=> {
console.log("Clicked linkit: ");
var attrs=e.currentTarget.attributes;
var m=this.state.currentNlink;
var c=1;
var s=1;
for (var a=0;a<attrs.length;a++)
{
console.log(attrs[a].name+"="+attrs[a].value);
switch (attrs[a].name)
{
case 'data-module':
m=parseInt(attrs[a].value,10);
break;
case 'data-chapter':
c=parseInt(attrs[a].value,10)-1;
break;
case 'data-section':
s=parseInt(attrs[a].value,10)-1;
break;
default:
break;
}
}
console.log(m+"/"+c+"/"+s);
this.goto(m,c,s);
}
componentDidMount() { // first time in DOM
var i;
var els = document.getElementsByClassName('popit');
for(i = 0; i < els.length; i++) {
els[i].addEventListener("click", this.popitClicked);
}
var pdfit = document.getElementsByClassName('pdfit');
for(i = 0; i < pdfit.length; i++) {
pdfit[i].addEventListener("click", this.PDFLink);
}
var externallinkit = document.getElementsByClassName('externallinkit');
for(i = 0; i < externallinkit.length; i++) {
externallinkit[i].addEventListener("click", this.ExternalLink);
}
var linkit = document.getElementsByClassName('linkit');
for(i = 0; i < linkit.length; i++) {
linkit[i].addEventListener("click", this.linkitClicked);
}
}
componentDidUpdate() { // DOM updated
var i;
var els = document.getElementsByClassName('popit');
for(i = 0; i < els.length; i++) {
els[i].addEventListener("click", this.popitClicked);
}
var pdfit = document.getElementsByClassName('pdfit');
for(i = 0; i < pdfit.length; i++) {
pdfit[i].addEventListener("click", this.PDFLink);
}
var externallinkit = document.getElementsByClassName('externallinkit');
for(i = 0; i < externallinkit.length; i++) {
externallinkit[i].addEventListener("click", this.ExternalLink);
}
var linkit = document.getElementsByClassName('linkit');
for(i = 0; i < linkit.length; i++) {
linkit[i].addEventListener("click", this.linkitClicked);
}
}
pdfResize(p_delay)
{
setTimeout(function(){
var pdf=document.getElementsByClassName("react-pdf__Document")[0];
var inner=document.getElementsByClassName("pdf-inner")[0];
inner.style.display="block";
var pg_r=inner.getBoundingClientRect();
var pdf_r=pdf.getBoundingClientRect();
var scale=pg_r.height/pdf_r.height;
pdf.style.transform="scale("+scale+")";
inner.style.width=Math.floor(pdf_r.width*scale)+"px";
},p_delay);
}
popup_close=()=>{
this.setState({popup:""});
}
pdf_close=()=>{
this.setState({pdf:""});
}
caret_left=()=>{
console.log("caret_left");
var nlink=this.state.config.nlinks[this.state.currentNlink];
var chap,sect;
if (nlink.currentChapter>0 || nlink.chapters[nlink.currentChapter].currentSection>0)
{
if (nlink.currentChapter < nlink.chapters.length)
{
if (nlink.chapters[nlink.currentChapter].currentSection > 0)
{
this.goto(this.state.currentNlink, nlink.currentChapter, nlink.chapters[nlink.currentChapter].currentSection-1);
}
else
{
chap=nlink.currentChapter-1;
sect=nlink.chapters[chap].sections.length-1;
if (sect<0) sect=0;
this.goto(this.state.currentNlink,chap,sect);
}
}
else // on the test!
{
chap=nlink.currentChapter-1;
sect=nlink.chapters[chap].sections.length-1;
if (sect<0) sect=0;
this.goto(this.state.currentNlink,chap,sect);
}
}
}
caret_right=()=>{
console.log("caret_right");
var nlink=this.state.config.nlinks[this.state.currentNlink];
if (nlink.currentChapter < nlink.chapters.length)
{
if (nlink.chapters[nlink.currentChapter].currentSection < nlink.chapters[nlink.currentChapter].sections.length-1)
{
this.goto(this.state.currentNlink,nlink.currentChapter,nlink.chapters[nlink.currentChapter].currentSection+1);
}
else
{
this.goto(this.state.currentNlink,nlink.currentChapter+1,0);
}
}
}
goto=(p_module,p_chapter,p_section)=>{
const cf = JSON.parse(JSON.stringify(this.state.config));
if (p_module>=cf.nlinks.length) return;//invalid module
if (cf.nlinks[p_module].chapters.length>0 && p_chapter>cf.nlinks[p_module].chapters.length) return;//invalid chapter (nb doesn't test for chapters without tests)
if (p_chapter<cf.nlinks[p_module].chapters.length && cf.nlinks[p_module].chapters[p_chapter].sections.length>0 && p_section>=cf.nlinks[p_module].chapters[p_chapter].sections.length) return; // invalid section
for (var i=0;i<cf.nlinks.length;i++)
{
cf.nlinks[i].state=0;
}
for (i=0 ; i<cf.nlinks[p_module].chapters.length ; i++)
{
cf.nlinks[p_module].chapters[i].state=0;
if (cf.nlinks[p_module].chapters[i].sections.length>0)
{
for (var j=0 ; j<cf.nlinks[p_module].chapters[i].sections.length ; j++)
{
cf.nlinks[p_module].chapters[i].sections[j].state=0;
}
}
}
if (cf.nlinks[p_module].test) cf.nlinks[p_module].test.state=0;
cf.nlinks[p_module].state=1;
if (cf.nlinks[p_module].chapters.length>0)
{
cf.nlinks[p_module].currentChapter=p_chapter;
if (p_chapter<cf.nlinks[p_module].chapters.length)
{
cf.nlinks[p_module].chapters[p_chapter].state=1;
cf.nlinks[p_module].chapters[p_chapter].currentSection=p_section;
if (cf.nlinks[p_module].chapters[p_chapter].sections.length>0)
{
cf.nlinks[p_module].chapters[p_chapter].sections[cf.nlinks[p_module].chapters[p_chapter].currentSection].state=1;
}
}
else
{
cf.nlinks[p_module].test.state=1;
}
}
this.setState({
currentNlink: p_module,
config: cf,
mpanel:"", // reset mpanel to force fetch of html in next render
});
}
handleSubmit(event) {
var n=this.state.currentNlink;
console.log("submit");
for (var q=0;q<this.state.config.nlinks[n].test.questions.length;q++)
{
console.log(this.state.config.nlinks[n].test.questions[q].response);
}
const cf = JSON.parse(JSON.stringify(this.state.config));
cf.nlinks[n].test.submitted=1;
this.setState({
config: cf,
});
event.preventDefault();
}
fetchHTML(p_url, p_type)
{
fetch("pages/"+p_url)
.then((r) => r.text())
.then((text) =>{
// this is the html for the page!
// parse
console.log("html path = "+p_url);
var path=p_url.split('/');
path.pop();// remove filename
path=path.join('/'); // reform path
console.log("relative path = "+path);
text=text.replace(/src="/g,'src="./pages/'+path+"/");
text=text.replace(/src='/g,"src='./pages/"+path+"/");
switch (p_type)
{
case 'mpanel':
this.setState({mpanel:text});
break;
case 'popup':
this.setState({popup:text});
break;
default:
break;
}
});
}
render() { // App
const { pageNumber, numPages } = this.state; // for pdf
// construct variable items from state
var app="App ";
var nls=[];
var sbar={title:"",chapters:[],test:""};
var mpanel="";
var popup="";
var pdf="";
var n;
var home=false;
var module=false;
var resources=false;
var lnav=[];
for (n=0 ; n < this.state.config.nlinks.length ; n++)
{
if (this.state.config.nlinks[n].state===1)
{
if (this.state.config.nlinks[n].home)
{
home=true;
app+="home";
if (this.state.homelhs==='')
{
fetch("pages/"+this.state.config.nlinks[n].lhs_html_url)
.then((r) => r.text())
.then((text) =>{
// this is the html for home lhs
// parse
text=text.replace(/src="/g,'src="./pages/');
text=text.replace(/src='/g,"src='./pages/");
this.setState({homelhs:text});
});
}
if (this.state.homerhs==='')
{
fetch("pages/"+this.state.config.nlinks[n].rhs_html_url)
.then((r) => r.text())
.then((text) =>{
// this is the html for home lhs
// parse
text=text.replace(/src="/g,'src="./pages/');
text=text.replace(/src='/g,"src='./pages/");
this.setState({homerhs:text});
});
}
}
else if (this.state.config.nlinks[n].module)
{
module=true;
sbar.title=this.state.config.nlinks[n].label;
sbar.subtitle="CHAPTERS";
var c;
for (c=0 ; c<this.state.config.nlinks[n].chapters.length ; c++)
{
sbar.chapters.push(this.renderChapterLink(n,c));
if (this.state.config.nlinks[n].chapters[c].sections.length>0)
{
for (var s=0;s<this.state.config.nlinks[n].chapters[c].sections.length;s++)
{
lnav.push(this.renderChapterDot(n,c,s));
}
}
else
{
lnav.push(this.renderChapterDot(n,c,0));
}
}
app+="module";
if (this.state.config.nlinks[n].test)
{
sbar.test=this.renderTestLink(n);
lnav.push(this.renderChapterDot(n,c,0));
}
}
else if (this.state.config.nlinks[n].resources)
{
resources=true;
app+="resources";
}
if (this.state.mpanel && this.state.mpanel.length>0)
{
mpanel=(<div dangerouslySetInnerHTML={{__html: this.state.mpanel}}/>);
}
else if (this.state.config.nlinks[n].test && this.state.config.nlinks[n].test.state===1)
{
var questions=[];
for (var q=0; q<this.state.config.nlinks[n].test.questions.length; q++)
{
questions.push(this.renderQuestion(n,q));
}
mpanel=(
<form onSubmit={this.handleSubmit} className={this.state.config.nlinks[n].test.submitted?"submitted":""}>
<div className="title">{this.state.config.nlinks[n].test.title}</div>
<div>{questions}</div>
<input type="submit" className='submit' value='Submit' />
</form>
);
}
else if (this.state.config.nlinks[n].chapters.length>0)
{
var chap=this.state.config.nlinks[n].chapters[this.state.config.nlinks[n].currentChapter];
if (chap.sections.length>0)
{
this.fetchHTML(chap.sections[chap.currentSection].url,'mpanel');
}
else if (chap.url)
{
this.fetchHTML(chap.url,'mpanel');
}
}
else if (this.state.config.nlinks[n].resources)
{
this.fetchHTML(this.state.config.nlinks[n].url,'mpanel');
}
}
}
for (n=0 ; n < this.state.config.nlinks.length ; n++)
{
if (home)
{
if (this.state.config.nlinks[n].module)
{
nls.push(this.renderNavLink(n));
}
}
else if (module || resources)
{
nls.push(this.renderNavLink(n));
}
}
if (this.state.popup && this.state.popup.length>1)
{
popup=(
<div className="popup-alpha">
<div className="popup-inner">
<div className="popup-close" onClick={this.popup_close}>
<span className="bg"/>
<i className="fa fa-times-circle"/>
</div>
<div dangerouslySetInnerHTML={{__html: this.state.popup}}/>
</div>
</div>
);
}
if (this.state.pdf && this.state.pdf.length>1)
{
popup=(
<div className="pdf-alpha">
<div className="pdf-inner" ref={(ref) => this.pdfWrapper = ref}>
<div className="pdf-left">
<i className="fa fa-caret-left" onClick={this.previousPage}/>
</div>
<div className="pdf-right">
<i className="fa fa-caret-right" onClick={this.nextPage}/>
</div>
<div className="pdf-close " onClick={this.pdf_close}>
<span className="bg"/>
<i className="fa fa-times-circle"/>
</div>
<div className="pdf-info">
{pageNumber+"/"+numPages+" pages"}
</div>
<Document
file={"pages/"+this.state.pdf}
onLoadSuccess={this.onDocumentLoadSuccess}
>
<Page pageNumber={pageNumber} />
</Document>
</div>
</div>
);
}
return (
<div className={app}>
<div className="header-home">
<div className="header-logo"></div>
<div className="header-spacer"></div>
<div className="header-title">ANGIOTENSIN II</div>
<div className="header-subtitle">Training Portal</div>
</div>
<div className="nav">
<div className="pinner">
{ nls }
</div>
</div>
<div className="lower">
<div className="spacer"></div>
<div className="lhs" dangerouslySetInnerHTML={{__html: this.state.homelhs}} />
<div className="rhs" dangerouslySetInnerHTML={{__html: this.state.homerhs}} />
<div className="sbar">
<div className="sbar-header">
<div className="pinner">
<div className="sbar-header-title">
{sbar.title}
</div>
</div>
</div>
<div className="sbar-spacer30"></div>
<div className="sbar-panel">
<div className="sbar-panel-title">
{sbar.subtitle}
</div>
<ol className="chapters">
{sbar.chapters}
</ol>
{sbar.test}
</div>
</div>
<div className="mpanel">
{mpanel}
<div className="lnav">
<i className="fa fa-caret-left" onClick={this.caret_left} />
<div className="lnav-inner">
{lnav}
</div>
<i className="fa fa-caret-right" onClick={this.caret_right} />
</div>
</div>
</div>
<div className="lbanner"/>
{popup}
{pdf}
</div>
);
}
}
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment