class BnDetail { make(root) { this.root = root || n('div.bnDetail', n('script', {src: 'https://code.jquery.com/jquery-3.4.1.slim.min.js'}), n('script', {src: sitePath('/_/js/arrows.js')}), n('script', {src: sitePath('/_/js/bn.js')}), n('div.controls', n('button.save', 'Save to My Library'), n('button.publish', 'Publish to Public Library'), n('span.gap'), n('span.scenarioControls', n('span', 'Scenario:'), this.scenarioBox = n('select.scenario', n('option.none', 'None'), ), n('button.saveScenario', 'Save'), n('button.removeScenario', 'Remove'), n('button.renameScenario', 'Rename'), ), n('button.download', 'Download'), ), n('div.bnView', ), n('div.infoWindows', n('div.help', n('h2', 'Help'), n('div.tip.infoContent', n('p', `Hover over a node and click 'E' to set an effect, which will display the causal information with all other nodes below.`), n('p', `To see the effect of a `, n('em', 'combined'), ` set of causes, click 'C' (Cause) on one or more other nodes, which will display an information window below.`), n('p', `To focus on the causal information for just specific states, click the checkbox next to the state name. To select multiple such states, hold down 'Shift'.`), ), ), ), ); this.titleEl = this.root.querySelector('.title'); this.bnView = this.root.querySelector('.bnView'); } toHtml() { return this.root.outerHTML; } $handleUpdate(m) { let barMax = 100; //px if (m.title) { /// XXX Hack: Find a way of getting the page component if (document.body) { q('h1 .text').innerHTML = m.title; } else { this.titleEl.textContent = m.title; } } if (m.model) { this.bnView.querySelectorAll('.node').forEach(n => n.remove()); let nodes = m.model.map(node => n('div.node', {dataName: node.name}, /* Use transform, so that alignment is the same as what GeNIe thinks it is (i.e. based on centre point) */ {style: `left: ${node.pos[0]}px; top: ${node.pos[1]}px; transform:translate(-50%,-50%)`}, n('div.controls', n('a.setCause', {href: 'javascript:void(0)'}, 'C'), n('a.setEffect', {href: 'javascript:void(0)'}, 'E'), //n('a.menu', {href: 'javascript:void(0)'}, '\u22EF'), ), n('h3', node.title ?? node.name), n('div.states', node.states.map((s,i) => n('div.state', {dataIndex: i}, n('span.target', n('input', {type: 'checkbox'})), n('span.label', s), n('span.prob', (node.beliefs[i]*100).toFixed(1)), n('span.barParent', n('span.bar', {style: `width: ${node.beliefs[i]*barMax}%`}))), ), ), )); this.bnView.append(...nodes); this.bnView.append(n('script', ` bn.model = ${JSON.stringify(m.model)}; bn.drawArcs(); `)); } if (m.nodeBeliefs) { for (let [nodeName,beliefs] of Object.entries(m.nodeBeliefs)) { let nodeEl = this.bnView.querySelector(`div.node[data-name=${nodeName}]`); nodeEl.querySelectorAll('.state').forEach((state,i) => { state.querySelector('.prob').textContent = (beliefs[i]*100).toFixed(1); state.querySelector('.bar').style.width = (beliefs[i]*barMax)+'%'; }); } } /** != null is false only if value is null or undefined **/ if (m.scenariosEnabled != null) { this.root.querySelector('.scenarioControls').style.display = m.scenariosEnabled ? 'inline' : 'none'; } if (m.scenarios) { for (let scenario of m.scenarios) { this.scenarioBox.append(n('option', scenario.name, {value: scenario.id, dataScenario: JSON.stringify(scenario)})); } } if (m.temporary != null) { this.root.querySelector('button.publish').style.display = m.temporary ? 'none' : 'inline'; if (!m.temporary) { this.root.querySelector('button.save').textContent = 'Save'; } } if (m.visibility != null) { this.root.querySelector('button.publish').style.display = m.visibility == 'public' ? 'none' : 'inline'; this.root.querySelector('button.publish').disabled = m.visibility == 'review'; this.root.querySelector('button.publish').textContent = m.visibility == 'review' ? 'Pending Review' : 'Publish'; } if (m.nosave === true) { this.root.querySelector('button.save').style.display = 'none'; } } } class CbnList { make() { this.root = n('div.mainPage', n('div.sideBar', n('div.catIntro.box', n('h2', 'What is CAT?'), n('p', `CAT (the Causal Attribution Tool) is a tool for exploring different causal scenarios incorporating uncertainties and allowing you to entertain and test different hypotheses about what is causing what in those scenarios. CAT will also allow you to compare alternative ideas about the nature of causal attribution itself; that is, distinct criteria for judging causal attribution can (and will) be implemented – although we have implemented our own preferred criterion first, naturally, along with a couple of popular alternatives.`), n('div.controls', n('button', {href: sitePath('/what_is_cat')}, 'Read more...'), ), ), n('div.upload.box', n('h2', 'Load a Causal BN'), n('div.uploadChoice', n('div.dragAndDrop', n('div', 'Drag and Drop'), ), n('div.divider', n('span','')), n('form', {method:'post', action:'/upload', enctype:'multipart/form-data'}, n('div.chooseFile', n('div', 'Or browse:'), n('button', 'Upload...', {type:'button', onclick: `document.querySelector('input[name=uploadCbn]').click()`}), n('input', {type: 'file', name: 'uploadCbn', style: 'display:none', onchange: `this.form.submit()`}), ), ), ), n('div.uploadInfo', 'CAT can accept any files supported by GeNIe (including Netica and HUGIN files)', ), ), ), n('div.mainBns', this.myBnList = n('div.cbnList.box.myBns', n('h2', 'My Causal BNs'), this.myBnListContents = n('div.cbnListContents'), ), this.cbnList = n('div.cbnList.box', n('h2', 'Public Library of Causal BNs'), this.cbnListContents = n('div.cbnListContents'), ), ), ); } toHtml() { return this.root.outerHTML; } makeBnEntry(bn, o = {}) { return n('div.cbn.box', {dataBnId: bn.id}, n('a', {href: sitePath(`bn?id=${bn.id}`)}, n('h2', bn.name)), n('p.description', bn.description), n('div.fields', bn.author && n('div.field', n('label', 'Author:'), n('span', bn.author), ), bn.keywords && n('div.field', n('label', 'Keywords:'), n('span', bn.keywords), ), ), n('div.controls', o.editable ? n('button', 'Delete', {onclick:`deleteBn(${bn.id})`}) : '', n('button', 'Open', {href: sitePath(`bn?id=${bn.id}`)}), bn.visibility == 'review' ? n('button.acceptToPublic', 'Accept to Public', {onclick:`acceptBn(${bn.id})`}) : '', ), ); } $handleUpdate(m) { if (m.publicBns) { while (this.cbnListContents.hasChildNodes()) { this.cbnListContents.firstChild.remove(); } this.cbnListContents.append( ...m.publicBns.map(bn => this.makeBnEntry(bn)), ); } if (m.myBns) { while (this.myBnListContents.hasChildNodes()) { this.myBnListContents.firstChild.remove(); } this.myBnListContents.append( ...m.myBns.map(bn => this.makeBnEntry(bn, {editable:true})), ); } /*if (m.hasUser != null) { this.root.classList.add('noUser'); }*/ } } class Login { make(root, data) { if (data.step == 1) { this.root = n('div.userLogin', n('form.login.form.active', {method: 'post', action: '/login?step=2'}, n('div.field', n('label', 'Username:'), n('span', n('input', {name: 'username'})), ), n('div.field', n('label', 'Password:'), n('span', n('input', {name: 'password', type: 'password'})), ), n('div.controls', n('button', {onclick: 'changeUserLogin("create", this)', type:'button'}, 'Create Account'), n('button.login', 'Login'), ), ), n('form.register.form', {method: 'post', action: '/login?step=2®ister=1'}, n('div.field', n('label', 'Username:'), n('span', n('input', {name: 'username'})), ), n('div.field', n('label', 'Email:'), n('span', n('input', {name: 'email'})), ), n('div.field', n('label', 'Password:'), n('span', n('input', {name: 'password', type: 'password'})), ), n('div.field', n('label', 'Confirm password:'), n('span', n('input', {name: 'password2', type: 'password'})), ), n('div.controls', n('button', {onclick: 'changeUserLogin("login", this)', type:'button'}, 'Back to Login'), n('button.register', 'Register'), ), ), ); } else if (data.step == 2) { console.log(data); if (data.userExists) { this.root = n('div.contentPage', n('h2', 'Username already exists'), n('p', `It appears that username already exists. Please try with a different username.`), ); } else if (data.emailExists) { this.root = n('div.contentPage', n('h2', 'Email already exists'), n('p', `It appears that email already exists. Please try with a different email.`), ); } else if (data.register) { this.root = n('div.contentPage', n('h2', 'Thanks for registering!'), n('p', `Thanks, ${data.details.username}, for taking an interest in CAT: The Causal Attribution Tool. You can now login using the button at the top right of the screen.`), ); } else if (data.loginFailed) { this.root = n('div.contentPage', n('h2', 'Login failed'), n('p', `Apologies, your username and/or password did not match anything in our system.`), ); } } } } class Upload { make(root, data) { if (data.step == 1) { this.root = n('form', {method:'post', action:'/upload?step=2'}, n('div.field', n('label', 'Name:'), n('input', {type:'text', name:'name'}), ), n('div.field', n('label', 'Description:'), n('textarea', {name: 'description'}), ), n('input', {type:'hidden', name: 'key'}), n('button', {type:'submit'}, 'Next'), ); } else if (data.step == 2) { this.root = n('p', 'Uploaded!'); } else if (data.step == 'error') { this.root = n('div', n('h2', 'The BN upload encountered an error'), n('p', `It looks like our site can't (yet) handle the BN you tried to upload. At the moment, the site can only handle basic Bayesian networks, with no extensions.`), n('p.errorMessage', n('label','Our validation gave the following error: '), (data.msg ? data.msg : n('b', 'The validation process timed out.'))), n('p', `Here are some things you can try:`), n('ul', n('li', 'Keep the BN as simple as possible - include only discrete CPT nodes and nothing else'), n('li', 'Convert all continuous nodes to discrete'), n('li', 'Remove any constant nodes'), n('li', 'Remove utility nodes. Convert decision nodes to ordinary nodes'), n('li', 'Remove all comments or other floating text'), n('li', 'You may have better luck if the file is in GeNIe (.xdsl) format'), ), n('p', n('a', {href:'/'}, html`← Home`), ), ); } } $handleUpdate(data) { if (data.step == 1) { this.root.querySelector('[name=name]').setAttribute('value', data.name); this.root.querySelector('[name=description]').setAttribute('value', data.description); this.root.querySelector('[name=key]').setAttribute('value', data.key); } } } class WhatIsCat { make(x, data) { this.root = n('div.whatIsCat.contentPage', n('h2', 'What is Causal Attribution?'), n('p', `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`), ); this.root.innerHTML = data.contents.toString().replace(/images\//g, 'what_is_cat_files/images/'); } }