\r\n {options.map((option, index) => {\r\n const inputProps = {\r\n name,\r\n type: \"radio\",\r\n checked: value === option,\r\n onChange: ()=>this.handleChange(index),\r\n style:styleObj,\r\n disabled:!enabled\r\n };\r\n\r\n const optionText=insertVariablesIntoText(option, formValue);\r\n\r\n return (\r\n
\r\n \r\n \r\n {optionText}\r\n \r\n
\r\n );\r\n })}\r\n
\r\n );\r\n }\r\n}\r\n\r\nexport { FormSelectRadio };\r\n","import React, { Component } from \"react\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport {\r\n getComponentValue,\r\n getStyle,\r\n addFontStyles,\r\n getActionObj,\r\n} from \"../form-helper\";\r\nimport { FORM_SELECT_TYPES, FORM_SEPERATORS } from \"constants/form\";\r\n\r\nconst isChecked = (option, value) => {\r\n if (check.nonEmptyString(value)) {\r\n const items = value.split(FORM_SEPERATORS[3]);\r\n if (check.nonEmptyArray(items)) {\r\n return items.some((item) => item === option);\r\n }\r\n }\r\n return false;\r\n};\r\n\r\nconst addValue = (option, value) => {\r\n if (check.nonEmptyString(value)) {\r\n const items = value.split(FORM_SEPERATORS[3]);\r\n if (check.nonEmptyArray(items)) {\r\n if (items.some((item) => item === option)) {\r\n return value;\r\n } else {\r\n items.push(option);\r\n return items.join(FORM_SEPERATORS[3]);\r\n }\r\n }\r\n }\r\n return option;\r\n};\r\n\r\nconst removeValue = (option, value) => {\r\n if (check.nonEmptyString(value)) {\r\n const items = value.split(FORM_SEPERATORS[3]);\r\n if (check.nonEmptyArray(items)) {\r\n if (items.some((item) => item === option)) {\r\n const newItems = items.filter((item) => item !== option);\r\n return newItems.join(FORM_SEPERATORS[3]);\r\n } else {\r\n return value;\r\n }\r\n }\r\n }\r\n return \"\";\r\n};\r\n\r\nclass FormSelectCheckbox extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleChange = this.handleChange.bind(this);\r\n this.updateAction = this.updateAction.bind(this);\r\n\r\n const { fontObj } = props;\r\n const styleObj = addFontStyles(getStyle(props), fontObj);\r\n this.state = { styleObj };\r\n }\r\n\r\n handleChange(index, checked) {\r\n const { onChange, name, type, options, action } = this.props;\r\n\r\n const option = options[index];\r\n const value = this.getValue();\r\n if (type === FORM_SELECT_TYPES.CHECKBOX) {\r\n const newValue = checked\r\n ? addValue(option, value)\r\n : removeValue(option, value);\r\n check.nonEmptyString(action)?onChange(name, newValue, action):onChange(name, newValue);\r\n } else if (type === FORM_SELECT_TYPES.CHECKBOX_RADIO) {\r\n const newValue = checked ? option : \"\";\r\n check.nonEmptyString(action)?onChange(name, newValue, action):onChange(name, newValue);\r\n }\r\n }\r\n\r\n getValue() {\r\n const { formValue, name } = this.props;\r\n\r\n return getComponentValue(name, formValue);\r\n }\r\n\r\n componentDidMount(){\r\n const actionObj = getActionObj(null, this.props);\r\n //console.log('componentDidMount', actionObj)\r\n this.updateAction(actionObj) \r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n const actionObj = getActionObj(prevProps, this.props);\r\n //console.log('componentDidUpdate', actionObj)\r\n this.updateAction(actionObj);\r\n }\r\n\r\n updateAction(actionObj){\r\n if (check.nonEmptyObject(actionObj)) {\r\n if (check.nonEmptyObject(actionObj.styles)){\r\n const { styleObj } = this.state;\r\n const newStyleObj = { ...styleObj, ...actionObj.styles };\r\n\r\n this.setState({ styleObj: newStyleObj });\r\n }\r\n else if (check.nonEmptyString(actionObj.message)){\r\n }\r\n } \r\n }\r\n\r\n render() {\r\n const { name, enabled, options } = this.props;\r\n const { styleObj } = this.state;\r\n\r\n const value = this.getValue();\r\n\r\n return (\r\n \r\n {options.map((option, index) => {\r\n const checked = isChecked(option, value);\r\n const inputProps = {\r\n name,\r\n type: \"checkbox\",\r\n checked,\r\n onChange: (e) =>\r\n this.handleChange(index, e.target.checked),\r\n style: styleObj,\r\n disabled: !enabled,\r\n };\r\n return (\r\n
\r\n \r\n \r\n {option}\r\n \r\n
\r\n );\r\n })}\r\n
\r\n );\r\n }\r\n}\r\n\r\nexport { FormSelectCheckbox };\r\n","import React, { Component } from \"react\";\r\n\r\nimport { getComponentValue, getStyle, addFontStyles } from \"../form-helper\";\r\n\r\nclass FormSelectCheckboxSingle extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleChange = this.handleChange.bind(this);\r\n\r\n const { fontObj } = props;\r\n const styleObj = addFontStyles(getStyle(props), fontObj);\r\n this.state = { styleObj };\r\n }\r\n\r\n handleChange(checked) {\r\n const { onChange, name } = this.props;\r\n const newValue = checked ? \"checked\" : \"\";\r\n onChange(name, newValue);\r\n }\r\n\r\n getValue() {\r\n const { formValue, name } = this.props;\r\n\r\n return getComponentValue(name, formValue);\r\n }\r\n\r\n render() {\r\n const { name, enabled } = this.props;\r\n const { styleObj } = this.state;\r\n\r\n const value = this.getValue();\r\n const checked = value === \"checked\";\r\n const inputProps = {\r\n name,\r\n type: \"checkbox\",\r\n checked,\r\n onChange: (e) => this.handleChange(e.target.checked),\r\n style: styleObj,\r\n disabled: !enabled,\r\n };\r\n\r\n return (\r\n {\r\n if (check.nonEmptyString(action)){\r\n const items=action.split(\"=\");\r\n\r\n if (check.nonEmptyArray(items) && items.length===3){\r\n const parts=items[2].split(',');\r\n\r\n if (parts.some(item=>item.toLowerCase()===value.toLowerCase())){\r\n return `${items[0]}=${items[1]}`;\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\nclass FormSelectDropdown extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleChange = this.handleChange.bind(this);\r\n\r\n const { type } = props;\r\n this.isMultiple = type === FORM_SELECT_TYPES.DROPDOWN_MULTI;\r\n }\r\n\r\n handleChange(e) {\r\n const { onChange, name, options, action } = this.props;\r\n\r\n if (this.isMultiple) {\r\n const dropdownOptions = e.target.options;\r\n\r\n const selected = [];\r\n for (let i = 0; i < dropdownOptions.length; i++) {\r\n if (dropdownOptions[i].selected) {\r\n selected.push(i);\r\n }\r\n }\r\n\r\n const value = check.nonEmptyArray(selected)\r\n ? selected\r\n .map((item) => options[item])\r\n .join(FORM_SEPERATORS[3])\r\n : \"\";\r\n\r\n //console.log(\"Set Value\", name, value);\r\n onChange(name, value);\r\n } else {\r\n const index = parseInt(e.target.value);\r\n const value = options[index];\r\n\r\n //console.log(\"Set Value\", name, value);\r\n\r\n if (check.nonEmptyString(action)){\r\n const selectAction=getAction(action, value);\r\n \r\n if (check.nonEmptyString(selectAction)){\r\n onChange(name, value, selectAction);\r\n return;\r\n }\r\n }\r\n onChange(name, value);\r\n }\r\n }\r\n\r\n getValue() {\r\n const { formValue, name, options } = this.props;\r\n const value = getComponentValue(name, formValue);\r\n\r\n if (this.isMultiple) {\r\n const values = check.nonEmptyString(value)\r\n ? value.split(FORM_SEPERATORS[3])\r\n : [];\r\n const indexes = values.map((value) =>\r\n options.findIndex((option) => value === option)\r\n );\r\n \r\n return indexes;\r\n } else {\r\n const index = options.findIndex((option) => value === option);\r\n\r\n return index;\r\n }\r\n }\r\n\r\n render() {\r\n const { enabled, options, fontObj } = this.props;\r\n const styleObj = addFontStyles(getStyle(this.props), fontObj);\r\n const value = this.getValue();\r\n const selectProps = {\r\n style: styleObj,\r\n value,\r\n onChange: this.handleChange,\r\n multiple: this.isMultiple,\r\n };\r\n\r\n return (\r\n \r\n \r\n {options.map((option, index) => {\r\n const optionProps = {\r\n key: index,\r\n value: index,\r\n style: styleObj,\r\n disabled: !enabled,\r\n };\r\n return {option} ;\r\n })}\r\n \r\n
\r\n );\r\n }\r\n}\r\n\r\nexport { FormSelectDropdown };\r\n","import React, { Component } from \"react\";\r\n\r\nimport { FormSelectRadio } from \"./form-select-radio\";\r\nimport { FormSelectCheckbox } from \"./form-select-checkbox\";\r\nimport { FormSelectCheckboxSingle } from \"./form-select-checkbox-single\";\r\nimport { FormSelectDropdown } from \"./form-select-dropdown\";\r\n\r\nimport { FORM_SELECT_TYPES } from \"constants/form\";\r\n\r\nclass FormSelect extends Component {\r\n state = {};\r\n render() {\r\n const { type } = this.props;\r\n\r\n if (type === FORM_SELECT_TYPES.RADIO) {\r\n return ;\r\n } else if (\r\n type === FORM_SELECT_TYPES.CHECKBOX ||\r\n type === FORM_SELECT_TYPES.CHECKBOX_RADIO\r\n ) {\r\n const { single } = this.props;\r\n\r\n return single === \"true\" ? (\r\n \r\n ) : (\r\n \r\n );\r\n } else if (\r\n type === FORM_SELECT_TYPES.DROPDOWN ||\r\n type === FORM_SELECT_TYPES.DROPDOWN_MULTI\r\n ) {\r\n return ;\r\n }\r\n\r\n return null;\r\n }\r\n}\r\n\r\nexport { FormSelect };\r\n","import React, { Component } from \"react\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { getStyle, addFontStyles, getActionObj } from \"../form-helper\";\r\n\r\nclass FormButton extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n const { fontObj } = props;\r\n const styleObj = addFontStyles(getStyle(props), fontObj);\r\n\r\n this.state = { styleObj };\r\n\r\n this.handleClick = this.handleClick.bind(this);\r\n }\r\n\r\n componentDidMount() {\r\n const actionObj = getActionObj(null, this.props);\r\n //console.log(\"componentDidMount\", actionObj);\r\n\r\n this.updateAction(actionObj);\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n const actionObj = getActionObj(prevProps, this.props);\r\n //console.log(\"componentDidUpdate\", actionObj);\r\n\r\n this.updateAction(actionObj);\r\n }\r\n\r\n updateAction(actionObj) {\r\n if (check.nonEmptyObject(actionObj)) {\r\n if (check.nonEmptyObject(actionObj.styles)) {\r\n const { styleObj } = this.state;\r\n const newStyleObj = { ...styleObj, ...actionObj.styles };\r\n\r\n this.setState({ styleObj: newStyleObj });\r\n } else if (check.nonEmptyString(actionObj.message)) {\r\n }\r\n }\r\n }\r\n\r\n handleClick() {\r\n const {\r\n enabled,\r\n onClick,\r\n onAction,\r\n onChange,\r\n name,\r\n action,\r\n changeAction,\r\n } = this.props;\r\n\r\n if (enabled) {\r\n if (check.nonEmptyString(action)) {\r\n if (check.nonEmptyString(changeAction)) {\r\n const items=changeAction.split(\",\");\r\n\r\n if (check.nonEmptyArray(items) && items.length>1 && items[1]===\"true\"){\r\n onChange(items[0], name, action);\r\n }\r\n else{\r\n onChange(name, changeAction, action);\r\n }\r\n } else {\r\n onAction(action);\r\n }\r\n } else {\r\n onClick(name);\r\n }\r\n }\r\n }\r\n\r\n render() {\r\n const { text, enabled } = this.props;\r\n const { styleObj } = this.state;\r\n const _styleObj = { ...styleObj };\r\n\r\n if (!enabled && styleObj.cursor) {\r\n try {\r\n delete _styleObj.cursor;\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n }\r\n const props = { style: _styleObj, onClick: this.handleClick };\r\n\r\n return (\r\n \r\n {text} \r\n
\r\n );\r\n }\r\n}\r\n\r\nexport { FormButton };\r\n","import React, { Component } from \"react\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { getStyle } from \"../form-helper\";\r\n\r\nclass FormFile extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.fileInputRef = React.createRef();\r\n this.handleClick = this.handleClick.bind(this);\r\n this.handleChange = this.handleChange.bind(this);\r\n }\r\n\r\n handleClick() {\r\n const { enabled } = this.props;\r\n if (enabled) {\r\n this.fileInputRef.current.click();\r\n }\r\n }\r\n\r\n handleChange(e) {\r\n const { field } = this.props;\r\n\r\n if (check.nonEmptyString(field)) {\r\n const { onChange } = this.props;\r\n const selectedFiles = e.target.files;\r\n\r\n if (selectedFiles && selectedFiles.length > 0) {\r\n const selectedFile = e.target.files[0];\r\n\r\n onChange(field, selectedFile.name);\r\n }\r\n }\r\n }\r\n\r\n render() {\r\n const { text, enabled } = this.props;\r\n const styleObj = getStyle(this.props);\r\n if (!enabled){\r\n delete styleObj.cursor;\r\n }\r\n const buttonProps = { style: styleObj, onClick: this.handleClick };\r\n const inputProps = {\r\n type: \"file\",\r\n hidden: true,\r\n onChange: this.handleChange,\r\n ref: this.fileInputRef,\r\n };\r\n\r\n return (\r\n \r\n \r\n {text} \r\n \r\n );\r\n }\r\n}\r\n\r\nexport { FormFile };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getApiEndpointUrl } from \"redux/reducers/settings/app/selectors\";\r\n\r\nimport { getAscxImageUrl } from \"utils/get-ascx-image-url\";\r\nimport {Image} from 'components/presentation/image'\r\nimport { getStyle } from \"./form-helper\";\r\n\r\n\r\nclass FormImage extends Component {\r\n state = {};\r\n render() {\r\n const { src, name, apiEndpointUrl } = this.props;\r\n const styleObj = getStyle(this.props);\r\n const altName = `image - ${name}`;\r\n const imageProps = {\r\n style: styleObj,\r\n src: getAscxImageUrl(src, apiEndpointUrl),\r\n alt: altName\r\n };\r\n\r\n return (\r\n \r\n
\r\n {/*
*/}\r\n
\r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n \r\n const settingsData = getSettingsData(store);\r\n const appSettingsData = getAppSettingsData(settingsData);\r\n \r\n return {\r\n apiEndpointUrl: getApiEndpointUrl(appSettingsData),\r\n };\r\n };\r\n \r\n FormImage = connect(mapStoreToProps)(FormImage);\r\n\r\nexport { FormImage };\r\n","import React, { Component } from \"react\";\r\nimport { getStyle, getComponentValue } from \"./form-helper\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nclass FormHotspot extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n const { checkbox } = props;\r\n this.hasCheckbox = check.nonEmptyString(checkbox);\r\n\r\n if (this.hasCheckbox) {\r\n const items = checkbox.split(\",\");\r\n if (check.nonEmptyArray(items)) {\r\n this.checkBoxPosition = [items[0], items[1]];\r\n this.checkBoxSize = items.length > 2 ? items[2] : 1;\r\n }\r\n }\r\n\r\n this.handleClick = this.handleClick.bind(this);\r\n }\r\n\r\n handleClick() {\r\n const { enabled, name } = this.props;\r\n\r\n if (enabled) {\r\n if (this.hasCheckbox) {\r\n this.handleChecked(null);\r\n } else {\r\n const { onClick } = this.props;\r\n\r\n onClick(name);\r\n }\r\n }\r\n }\r\n\r\n handleChecked(_checked) {\r\n const { enabled, onChange, name } = this.props;\r\n if (enabled) {\r\n const checked = check.boolean(checked)\r\n ? _checked\r\n : !this.getChecked();\r\n const newValue = checked ? \"checked\" : \"\";\r\n onChange(name, newValue);\r\n }\r\n }\r\n\r\n getChecked() {\r\n const { formValue, name } = this.props;\r\n\r\n const value = getComponentValue(name, formValue);\r\n return value === \"checked\";\r\n }\r\n\r\n render() {\r\n const { enabled, name } = this.props;\r\n const styleObj = getStyle(this.props);\r\n\r\n if (!enabled) {\r\n styleObj.cursor = \"default\";\r\n }\r\n styleObj.display = \"inline-block\";\r\n const hotSpotProps = {\r\n style: styleObj,\r\n onClick: this.handleClick,\r\n };\r\n\r\n if (this.hasCheckbox) {\r\n const inputProps = {\r\n name,\r\n type: \"checkbox\",\r\n checked: this.getChecked(),\r\n onChange: (e) => this.handleChecked(e.target.checked),\r\n style: { transform: `scale(${this.checkBoxSize})` },\r\n disabled: !enabled,\r\n };\r\n const divProps = {\r\n style:{\r\n marginLeft: `${this.checkBoxPosition[0]}px`,\r\n marginTop: `${this.checkBoxPosition[1]}px`,\r\n }\r\n };\r\n\r\n return (\r\n \r\n );\r\n }\r\n\r\n return
;\r\n }\r\n}\r\n\r\nexport { FormHotspot };\r\n","import { check } from '@xams-utils/check-types';\r\nimport React, { Component } from 'react';\r\n\r\nclass FormDelay extends Component {\r\n state = { } \r\n\r\n constructor(props){\r\n super(props);\r\n\r\n this.handleTimer=this.handleTimer.bind(this);\r\n }\r\n\r\n handleTimer(){\r\n const {action, onAction}=this.props;\r\n\r\n onAction(action);\r\n }\r\n\r\n componentDidMount(){\r\n const {delay, action}=this.props;\r\n if (check.nonEmptyString(delay) && check.nonEmptyString(action)){\r\n const _delay=parseInt(delay);\r\n\r\n if (!isNaN(_delay)){\r\n this.timer=setTimeout(this.handleTimer, _delay * 1000)\r\n }\r\n }\r\n }\r\n\r\n componentWillUnmount(){\r\n clearTimeout(this.timer);\r\n }\r\n\r\n render() { \r\n return null;\r\n }\r\n}\r\n \r\nexport {FormDelay}","import React, { Component } from \"react\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { FormField } from \"./field/form-field\";\r\nimport { FormSelect } from \"./select/form-select\";\r\nimport { FormButton } from \"./button/form-button\";\r\nimport { FormFile } from \"./button/form-file\";\r\nimport { FormRow } from \"./form-row\";\r\nimport { FormImage } from \"./form-image\";\r\nimport { FormHotspot } from \"./form-hotspot\";\r\nimport { FormDelay } from \"./form-delay\";\r\n\r\nimport { getStyle } from \"./form-helper\";\r\n\r\nimport { FORM_TYPES } from \"constants/form\";\r\n\r\nclass FormCell extends Component {\r\n state = {};\r\n\r\n displayComponents() {\r\n const { components, formValue, onChange, onClick, onAction, formAction, enabled, fontObj } =\r\n this.props;\r\n\r\n const styleObj = getStyle(this.props);\r\n\r\n return (\r\n \r\n {components.map((_component, index) => {\r\n const { component } = _component;\r\n const props = {\r\n key: index,\r\n formValue,\r\n onChange,\r\n onClick,\r\n onAction,\r\n enabled,\r\n fontObj,\r\n formAction,\r\n ..._component,\r\n };\r\n\r\n if (component === FORM_TYPES.FIELD) {\r\n return ;\r\n } else if (component === FORM_TYPES.SELECT) {\r\n return ;\r\n } else if (component === FORM_TYPES.BUTTON) {\r\n return ;\r\n } else if (component === FORM_TYPES.FILE) {\r\n return ;\r\n } else if (component === FORM_TYPES.IMAGE) {\r\n return ;\r\n } else if (component === FORM_TYPES.HOTSPOT) {\r\n return ;\r\n } else if (component === FORM_TYPES.DELAY) {\r\n return ; \r\n }\r\n\r\n return null;\r\n })}\r\n
\r\n );\r\n }\r\n\r\n displayRows() {\r\n const { rows, formValue, onChange, onClick, enabled } = this.props;\r\n const styleObj = getStyle(this.props);\r\n\r\n return (\r\n \r\n {rows.map((row, index) => {\r\n const props = {\r\n key: index,\r\n onChange,\r\n onClick,\r\n enabled,\r\n formValue,\r\n ...row,\r\n };\r\n\r\n return ;\r\n })}\r\n
\r\n );\r\n }\r\n\r\n render() {\r\n const { components, rows } = this.props;\r\n\r\n if (check.nonEmptyArray(rows)) {\r\n return this.displayRows();\r\n } else if (check.nonEmptyArray(components)) {\r\n return this.displayComponents();\r\n }\r\n\r\n const styleObj = getStyle(this.props);\r\n\r\n return
;\r\n }\r\n}\r\n\r\nexport { FormCell };\r\n","import React, { Component } from \"react\";\r\n\r\nimport { FormCell } from \"./form-cell\";\r\nimport { getStyle, rowStateHidden } from \"./form-helper\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nconst isStringBoolean = (string) => {\r\n if (check.nonEmptyString(string)) {\r\n return string === \"true\" || string === \"false\";\r\n }\r\n\r\n return false;\r\n};\r\n\r\nclass FormRow extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.timer = null;\r\n\r\n this.handleFinishedTimer = this.handleFinishedTimer.bind(this);\r\n }\r\n\r\n componentDidUpdate() {\r\n const { finished, enabled, rowFamily, rowStates, rowState } =\r\n this.props;\r\n\r\n const isFinished =\r\n check.nonEmptyString(finished) && finished === \"true\";\r\n\r\n if (\r\n isFinished &&\r\n enabled &&\r\n !rowStateHidden(rowState, rowStates, rowFamily)\r\n ) {\r\n this.setTimer = setTimeout(this.handleFinishedTimer, 1000);\r\n }\r\n }\r\n\r\n componentWillUnmount() {\r\n clearTimeout(this.timer);\r\n }\r\n\r\n handleFinishedTimer() {\r\n const { onClick } = this.props;\r\n onClick(\"finished\", true);\r\n }\r\n\r\n getRowVisible() {}\r\n\r\n render() {\r\n const {\r\n cells,\r\n onChange,\r\n onClick,\r\n onAction,\r\n formAction,\r\n enabled,\r\n formValue,\r\n fontObj,\r\n rowFamily,\r\n rowFamilyVisible,\r\n rowStates,\r\n rowState,\r\n } = this.props;\r\n const styleObj = getStyle(this.props, { display: \"flex\" });\r\n\r\n // console.log('Render');\r\n // console.log('formAction', formAction)\r\n // console.log('rowFamily', rowFamily)\r\n // console.log('rowFamilyVisible', rowFamilyVisible);\r\n // console.log('');\r\n\r\n if (isStringBoolean(rowFamilyVisible)) {\r\n let rowVisible = rowFamilyVisible === \"true\";\r\n\r\n if (check.nonEmptyString(formAction)) {\r\n //debugger;\r\n const a = formAction.split(\",\");\r\n\r\n for (let i = 0; i < a.length; i++) {\r\n const b = a[i].split(\"=\");\r\n\r\n if (b[1] === \"variable\" && b[0] === rowFamily) {\r\n //debugger;\r\n const c = b[2].split(\":\");\r\n\r\n if (c[0] === \"visible\" && isStringBoolean(c[1])) {\r\n rowVisible = c[1] === \"true\";\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!rowVisible) {\r\n return null;\r\n }\r\n }\r\n\r\n if (rowStateHidden(rowState, rowStates, rowFamily)) {\r\n return null;\r\n }\r\n\r\n return (\r\n \r\n {cells.map((cell, index) => {\r\n const props = {\r\n key: index,\r\n onChange,\r\n onAction,\r\n formAction,\r\n onClick,\r\n enabled,\r\n formValue,\r\n fontObj,\r\n ...cell,\r\n };\r\n\r\n return ;\r\n })}\r\n
\r\n );\r\n }\r\n}\r\n\r\nexport { FormRow };\r\n","import React, { Component } from \"react\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { getFormValue, getFormSelected, addSpacesToName, insertVariablesIntoText } from \"./form-helper\";\r\n\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\nclass FormPopulated extends Component {\r\n state = {};\r\n\r\n getFieldValue(fieldValue){\r\n const { value, seps } = this.props;\r\n \r\n const formValue = getFormValue(value, seps);\r\n const newValue=insertVariablesIntoText(fieldValue, formValue);\r\n\r\n return newValue;\r\n }\r\n\r\n displayPopulated() {\r\n const { value, seps } = this.props;\r\n const formValue = getFormValue(value, seps);\r\n const items = [];\r\n\r\n for (const property in formValue) {\r\n items.push([property, formValue[property]]);\r\n }\r\n\r\n if (!check.nonEmptyArray(items)){\r\n return null;\r\n }\r\n \r\n return (\r\n \r\n \r\n Populated: \r\n \r\n {check.nonEmptyArray(items)\r\n ? items.map((item, index) => {\r\n return (\r\n \r\n {addSpacesToName(item[0])} : {this.getFieldValue(item[1])}\r\n \r\n );\r\n })\r\n : \"None\"}\r\n
\r\n );\r\n }\r\n\r\n displaySelected() {\r\n const { value, seps } = this.props;\r\n const formSelected = getFormSelected(value, seps);\r\n\r\n if (check.nonEmptyString(formSelected) && formSelected!==\"finished\") {\r\n return (\r\n \r\n \r\n Selected: {formSelected}\r\n \r\n
\r\n );\r\n }\r\n\r\n return null;\r\n }\r\n render() {\r\n return (\r\n \r\n {this.displaySelected()}\r\n {this.displayPopulated()}\r\n
\r\n );\r\n }\r\n}\r\n\r\nexport { FormPopulated };\r\n","import React, { Component } from \"react\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { FormConfirmation } from \"./form-confirmation\";\r\nimport { FormRow } from \"./form-row\";\r\nimport { FormPopulated } from \"./form-populated\";\r\nimport {\r\n getStyle,\r\n getFontStyles,\r\n createFormValue,\r\n getFormValue,\r\n getFormAction,\r\n hasFormBeenSelected,\r\n addSpacesToName,\r\n getInitialValues,\r\n isChangeRowState,\r\n getRowState,\r\n} from \"./form-helper\";\r\n\r\nimport { FORM_SEPERATORS } from \"constants/form\";\r\n\r\nconst getInitialRowState=(formData, value)=>{\r\n if (check.nonEmptyString(value)){\r\n const items=value.split(FORM_SEPERATORS[0]);\r\n\r\n if (check.nonEmptyArray(items) && items.length>2){\r\n const action=items[2];\r\n const rowState=getRowState(action); \r\n\r\n if (check.assigned(rowState)){\r\n return rowState;\r\n }\r\n }\r\n }\r\n\r\n const {rowState}=formData;\r\n return rowState;\r\n}\r\n\r\nclass Form extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleClick = this.handleClick.bind(this);\r\n this.handleChange = this.handleChange.bind(this);\r\n this.handleConfirmation = this.handleConfirmation.bind(this);\r\n this.handleAction = this.handleAction.bind(this);\r\n\r\n const { value, disabled, formData } = props;\r\n const selected = hasFormBeenSelected(value, FORM_SEPERATORS);\r\n const rowState = getInitialRowState(formData, value);\r\n const enabled = !disabled && !selected;\r\n\r\n this.state = {\r\n enabled,\r\n showConfirmation: false,\r\n confirmationName: null,\r\n formAction: \"\",\r\n rowState,\r\n };\r\n }\r\n\r\n handleClick(name, bypassConfirmation=false) {\r\n if (bypassConfirmation){\r\n this.handleConfirmation(true, name);\r\n }\r\n else{\r\n this.setState({ showConfirmation: true, confirmationName: name });\r\n }\r\n }\r\n\r\n handleConfirmation(confirmed, _name=null) {\r\n const { onChange, value } = this.props;\r\n const newState = { showConfirmation: false, confirmationName: null };\r\n\r\n if (confirmed) {\r\n const { confirmationName } = this.state;\r\n const name=check.nonEmptyString(_name)?_name:confirmationName;\r\n //console.log(\"Clicked\", name);\r\n let formValue = \"\";\r\n let action = \"\";\r\n\r\n if (check.nonEmptyString(value)) {\r\n const items = value.split(FORM_SEPERATORS[0]);\r\n\r\n if (check.nonEmptyArray(items) && items.length > 1) {\r\n formValue = items[1];\r\n\r\n if (items.length > 2) {\r\n action = items[2];\r\n }\r\n }\r\n }\r\n const formName = addSpacesToName(name);\r\n const newValue = [formName, formValue, action].join(\r\n FORM_SEPERATORS[0]\r\n );\r\n\r\n onChange(newValue);\r\n\r\n newState.enabled = false;\r\n }\r\n\r\n this.setState(newState);\r\n }\r\n\r\n handleChange(name, _value, _action=null) {\r\n const { value, onChange } = this.props;\r\n\r\n let selected = \"\";\r\n let action = \"\";\r\n let formValue = {};\r\n\r\n if (check.nonEmptyString(value)) {\r\n const items = value.split(FORM_SEPERATORS[0]);\r\n if (check.nonEmptyArray(items)) {\r\n selected = items[0];\r\n action = items.length > 2 ? items[2] : \"\";\r\n const formValues = items[1].split(FORM_SEPERATORS[1]);\r\n if (check.nonEmptyArray(formValues)) {\r\n formValue = formValues.reduce((obj, item) => {\r\n if (check.nonEmptyString(item)) {\r\n const items = item.split(FORM_SEPERATORS[2]);\r\n if (check.nonEmptyArray(items)) {\r\n obj[items[0]] = items[1];\r\n }\r\n }\r\n return obj;\r\n }, {});\r\n }\r\n }\r\n }\r\n\r\n const formName = addSpacesToName(name);\r\n formValue[formName] = _value;\r\n\r\n const newValue = [\r\n selected,\r\n createFormValue(formValue, FORM_SEPERATORS),\r\n check.assigned(_action)?_action:action,\r\n ].join(FORM_SEPERATORS[0]);\r\n \r\n console.log(newValue);\r\n onChange(newValue);\r\n\r\n if (check.assigned(_action)){\r\n const rowState=getRowState(_action);\r\n\r\n if (check.assigned(rowState)){\r\n this.setState({rowState}) \r\n }\r\n }\r\n }\r\n\r\n handleAction(action) {\r\n //console.log('Action', action);\r\n // cookies=style=visibility:visible\r\n\r\n const rowState=getRowState(action); \r\n if (check.assigned(rowState)){\r\n this.setState({rowState}) \r\n }\r\n\r\n const { value, onChange } = this.props;\r\n let newValue = \";\";\r\n if (check.nonEmptyString(value)) {\r\n const items = value.split(FORM_SEPERATORS[0]);\r\n newValue = [items[0], items[1], action].join(FORM_SEPERATORS[0]);\r\n //console.log(\"newValue\",newValue);\r\n } else {\r\n newValue = [\"\", \"\", action].join(FORM_SEPERATORS[0]);\r\n //console.log(\"newValue\",newValue);\r\n }\r\n onChange(newValue);\r\n }\r\n\r\n displayForm() {\r\n const { formData } = this.props;\r\n const styleObj = getStyle(formData);\r\n \r\n\r\n return (\r\n \r\n {this.displayRows()}\r\n {this.displayPopulated()}\r\n
\r\n );\r\n }\r\n\r\n displayRows(){\r\n const { formData, value } = this.props;\r\n const { enabled, rowState } = this.state;\r\n const { rows, rowStates, rowStateDefault } = formData;\r\n const styleObj = getStyle(formData);\r\n const fontObj = getFontStyles(styleObj);\r\n\r\n const formValue = getFormValue(value, FORM_SEPERATORS);\r\n const formAction = getFormAction(value, FORM_SEPERATORS);\r\n\r\n return rows.map((row, index) => {\r\n const props = {\r\n key: index,\r\n onChange: this.handleChange,\r\n onAction: this.handleAction,\r\n onClickAction: this.handleClickAction,\r\n formAction,\r\n enabled,\r\n onClick: this.handleClick,\r\n formValue,\r\n rowState,\r\n rowStates,\r\n rowStateDefault,\r\n fontObj,\r\n seps: FORM_SEPERATORS,\r\n ...row,\r\n };\r\n\r\n return ;\r\n })\r\n\r\n\r\n }\r\n\r\n displayPopulated() {\r\n const { value } = this.props;\r\n\r\n if (hasFormBeenSelected(value, FORM_SEPERATORS)) {\r\n const props = { value, seps: FORM_SEPERATORS };\r\n return ;\r\n }\r\n return null;\r\n }\r\n\r\n displayConfirmation() {\r\n const { showConfirmation, confirmationName } = this.state;\r\n const props = {\r\n onConfirm: this.handleConfirmation,\r\n showConfirmation,\r\n confirmationName,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n render() {\r\n return (\r\n \r\n {this.displayForm()}\r\n {this.displayConfirmation()}\r\n \r\n );\r\n }\r\n}\r\n\r\nexport { Form };\r\n","import React, { Component } from \"react\";\r\nimport { DisableAnsweringContext } from \"components/pages/exam/disabled-context\";\r\nimport {Form} from \"./form\"\r\n\r\nclass FormAnswerForm extends Component {\r\n state = {};\r\n\r\n displayForm(disabled) {\r\n const onChange = disabled ? undefined : this.props.onChange;\r\n const props = { onChange, disabled, ...this.props };\r\n\r\n return ;\r\n }\r\n\r\n render() {\r\n return (\r\n \r\n {(disabledValue) => this.displayForm(disabledValue)}\r\n \r\n );\r\n }\r\n}\r\n\r\nexport { FormAnswerForm };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport { check } from '@xams-utils/check-types'\r\n\r\n// react\r\nimport { AnswerBox } from './answer-box'\r\nimport { FormAnswerForm } from '../answer_forms/form/form-answer-form'\r\nimport { FORM_SEPERATORS } from \"constants/form\";\r\n\r\n// redux (selectors)\r\nimport { getResumedAnswer, getFormData } from 'redux/reducers/exam/content/questions/question/form/selectors'\r\nimport { getInitialValues } from '../answer_forms/form/form-helper'\r\n\r\n\r\nconst getInitialAnswer = (props) => {\r\n\tconst formData = props.get('formData');\r\n\r\n\tif (formData){\r\n\t\tconst initialValues = getInitialValues(formData);\r\n\r\n\t\tif (check.nonEmptyArray(initialValues)) {\r\n\t\t\tconst initialValueString = initialValues.map(value => [value.name, value.value].join(FORM_SEPERATORS[2])).join(FORM_SEPERATORS[1])\r\n\t\t\tconst initialValue = [\"\", initialValueString, \"\"].join(FORM_SEPERATORS[0]);\r\n\r\n\t\t\treturn initialValue;\r\n\t\t}\r\n\t}\r\n\r\n\treturn undefined;\r\n\r\n}\r\n\r\n\r\n\r\n\r\nclass FormAnswerBox extends React.Component {\r\n\tconstructor(props) {\r\n\t\tsuper(props);\r\n\t\tthis.setUpExtractionFunctions();\r\n\t\tthis.setUpTransformationFunctions();\r\n\t}\r\n\r\n\t// extract information from questionData\r\n\t// --------------------------------------\r\n\tsetUpExtractionFunctions() {\r\n\t\tthis.extractInitialAnswer = () => {\r\n\t\t\tconst initialAnswer = getInitialAnswer(this.props.data);\r\n\r\n\t\t\treturn initialAnswer;\r\n\t\t};\r\n\t\tthis.extractResumedAnswer = () => {\r\n\t\t\treturn getResumedAnswer(this.props.data);\r\n\t\t};\r\n\t\tthis.extractFormData = () => {\r\n\t\t\tconst { data } = this.props;\r\n\t\t\tconst formData = getFormData(data);\r\n\t\t\treturn formData;\r\n\t\t};\r\n\t}\r\n\r\n\t// convert between answer values\r\n\t// --------------------------------------\r\n\tsetUpTransformationFunctions() {\r\n\t\tthis.getFormValue = (formAnswer) => formAnswer;\r\n\t\tthis.toFormValue = (localAnswer) => localAnswer;\r\n\t\tthis.toLocalAnswer = (formValue) => formValue;\r\n\t\tthis.toServerAnswer = (localAnswer) => {\r\n\t\t\treturn { answerText: localAnswer, options: [] };\r\n\t\t};\r\n\r\n\t}\r\n\r\n\trender() {\r\n\t\tconst answerBoxProps = {\r\n\t\t\tquestionData: this.props.data,\r\n\t\t\tFormComponent: FormAnswerForm,\r\n\t\t\ttoFormValue: this.toFormValue,\r\n\t\t\tgetFormValue: this.getFormValue,\r\n\t\t\ttoLocalAnswer: this.toLocalAnswer,\r\n\t\t\ttoServerAnswer: this.toServerAnswer,\r\n\t\t\textractFormData: this.extractFormData,\r\n\t\t\textractInitialAnswer: this.extractInitialAnswer,\r\n\t\t\textractResumedAnswer: this.extractResumedAnswer\r\n\t\t}\r\n\r\n\t\treturn ;\r\n\t}\r\n}\r\n\r\nFormAnswerBox.propTypes = {\r\n\tdata: PropTypes.object.isRequired\r\n}\r\n\r\n\r\nexport { FormAnswerBox }","import React, { Component } from \"react\";\r\n\r\nimport Button from \"@material-ui/core/Button\";\r\nimport Dialog from \"@material-ui/core/Dialog\";\r\nimport DialogActions from \"@material-ui/core/DialogActions\";\r\nimport DialogContent from \"@material-ui/core/DialogContent\";\r\nimport DialogContentText from \"@material-ui/core/DialogContentText\";\r\nimport DialogTitle from \"@material-ui/core/DialogTitle\";\r\nimport { check } from \"@xams-utils/check-types\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\nconst addValuesToLabel = (_label, values) => {\r\n const label = Object.keys(values).reduce((text, value) => {\r\n\r\n return text.replace(`~${value}~`, values[value]);\r\n }, _label);\r\n\r\n return label;\r\n};\r\n\r\nclass ConfirmationDialog extends Component {\r\n state = {};\r\n\r\n getButtons() {\r\n const { onClose } = this.props;\r\n const noProps = {\r\n onClick: () => onClose(false),\r\n variant: \"contained\",\r\n };\r\n const yesProps = {\r\n onClick: () => onClose(true),\r\n variant: \"contained\",\r\n color: \"primary\",\r\n autoFocus: true,\r\n };\r\n\r\n return (\r\n \r\n No \r\n Yes \r\n \r\n );\r\n }\r\n\r\n getContent() {\r\n const { label, values } = this.props;\r\n const lines = label.split(\"#\");\r\n const text = lines.map((line) => {\r\n if (check.nonEmptyString(line)) {\r\n if (line.indexOf(\"~\") !== -1) {\r\n const text = addValuesToLabel(line, values);\r\n return {text} ;\r\n }\r\n return (\r\n \r\n {line} \r\n
\r\n );\r\n }\r\n });\r\n\r\n return (\r\n \r\n \r\n {text}\r\n \r\n \r\n );\r\n }\r\n\r\n getTitle() {\r\n const title = \"Change Options\";\r\n\r\n return {title} ;\r\n }\r\n\r\n render() {\r\n const { open, onClose } = this.props;\r\n\r\n return (\r\n \r\n {this.getTitle()}\r\n {this.getContent()}\r\n {this.getButtons()}\r\n \r\n );\r\n }\r\n}\r\n\r\nexport { ConfirmationDialog };\r\n","// npm\r\nimport React, { Component } from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\n// material-ui\r\nimport Radio from \"@material-ui/core/Radio\";\r\nimport RadioGroup from \"@material-ui/core/RadioGroup\";\r\nimport FormControl from \"@material-ui/core/FormControl\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport FormControlLabel from \"@material-ui/core/FormControlLabel\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport TextField from \"@material-ui/core/TextField\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\n\r\n// react\r\nimport { DisableAnsweringContext } from \"components/pages/exam/disabled-context\";\r\nimport { ConfirmationDialog } from \"./confirmation-dialog\";\r\nimport { MultiLineTextEditor } from \"components/presentation/text_editors/multi_line\";\r\n\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport { keyListener, KEYS } from \"utils/key-listener\";\r\nimport { WordCount } from \"components/presentation/text_editors/word-count\";\r\n\r\n\r\nconst disableArrowKeyListeners = () => {\r\n keyListener.disable(KEYS.LEFT_ARROW);\r\n keyListener.disable(KEYS.RIGHT_ARROW);\r\n};\r\n\r\nconst enableArrowKeyListeners = () => {\r\n keyListener.enable(KEYS.LEFT_ARROW);\r\n keyListener.enable(KEYS.RIGHT_ARROW);\r\n};\r\n\r\n\r\nconst getOptions = (\r\n currentValue,\r\n { options },\r\n disabled,\r\n classes,\r\n displayDescriptionInOption\r\n) => {\r\n const radioClasses = {\r\n root: classes.radioRoot,\r\n checked: classes.radioChecked,\r\n };\r\n\r\n const getLabelNode = (label, description, checked) => (\r\n \r\n {label} \r\n {displayDescriptionInOption && checked && (\r\n \r\n {description}\r\n \r\n )}\r\n
\r\n );\r\n\r\n const getRadioButton = (checked) => (\r\n \r\n );\r\n\r\n return options.map(({ value, label, description }, index) => {\r\n const key = `${label}${index}`;\r\n const labelNode = getLabelNode(\r\n label,\r\n description,\r\n value === currentValue\r\n );\r\n const control = getRadioButton(value === currentValue);\r\n\r\n return (\r\n \r\n );\r\n });\r\n};\r\n\r\nconst displayOptions = (props, disabled) => {\r\n const onChange = disabled ? undefined : this.handleRadioOnChange;\r\n const { value, formData } = props;\r\n\r\n return (\r\n \r\n {getOptions(value, formData, disabled, props.classes)}\r\n \r\n );\r\n};\r\n\r\n// let McqAnswerForm = (props) => (\r\n// \t\r\n// \t\t\r\n// \t\t\t{value => getOptionGroup(props, value)}\r\n// \t\t \r\n// \t \r\n// )\r\n\r\nclass McqTextAnswerForm extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleRadioChange = this.handleRadioChange.bind(this);\r\n this.handleTextChange = this.handleTextChange.bind(this);\r\n this.handleConfirmation = this.handleConfirmation.bind(this);\r\n\r\n this.displayDescriptionInOption = false;\r\n this.confirmRadio = false;\r\n this.confirmationText =props.messages[MESSAGE_IDS.EXAM.CONFIRM_MULTI_TEXT_OPTION_CHANGE];\r\n\r\n this.state = { confirmation: false };\r\n }\r\n\r\n displayOptions() {\r\n const disabled = this.isDisabled();\r\n const onChange = disabled ? undefined : this.handleRadioChange;\r\n const { value, formData } = this.props;\r\n const radioValue = value ? value.radio : undefined;\r\n\r\n return (\r\n \r\n {getOptions(\r\n radioValue,\r\n formData,\r\n disabled,\r\n this.props.classes,\r\n this.displayDescriptionInOption\r\n )}\r\n \r\n );\r\n }\r\n\r\n handleRadioChange(radio) {\r\n const { onChange, value, messages } = this.props;\r\n const text = value ? value.text : null;\r\n\r\n if (check.nonEmptyString(text) && check.nonEmptyString(this.confirmationText)) {\r\n this.confirmRadio = radio.target.value;\r\n this.setState({ confirmation: true });\r\n } else {\r\n onChange({ radio });\r\n }\r\n }\r\n\r\n handleTextChange(text) {\r\n const { onChange } = this.props;\r\n\r\n onChange({ text });\r\n }\r\n\r\n displayText() {\r\n const { value } = this.props;\r\n const lines = 15;\r\n const textValue = value ? value.text : \"\";\r\n const radioValue = value ? value.radio : undefined;\r\n const textProps = {\r\n multiline: true,\r\n rowsMax: lines,\r\n rows: lines,\r\n InputLabelProps: {\r\n shrink: true,\r\n },\r\n label: \"Text\",\r\n variant: \"outlined\",\r\n fullWidth: true,\r\n onChange: this.handleTextChange,\r\n value: textValue,\r\n disabled: !radioValue,\r\n onFocus: disableArrowKeyListeners,\r\n onBlur: enableArrowKeyListeners, \r\n };\r\n\r\n return (\r\n \r\n {!this.displayDescriptionInOption && this.displayDescription()}\r\n {/* */}\r\n {this.getMultiLineTextEditor()}\r\n {this.getWordCount()}\r\n
\r\n );\r\n }\r\n\r\n getWordCount(){\r\n const {formData}=this.props;\r\n const { showWordCount } = formData;\r\n\r\n if (showWordCount){\r\n const { value, classes } = this.props;\r\n const textValue = value ? value.text : \"\";\r\n\r\n return
\r\n }\r\n\r\n return null;\r\n }\r\n\r\n getMultiLineTextEditor() {\r\n const { value, classes, formData } = this.props;\r\n\r\n let { lines } = formData;\r\n if (!check.number(lines)) {\r\n lines = 15;\r\n }\r\n\r\n const textValue = value ? value.text : \"\";\r\n const radioValue = value ? value.radio : undefined;\r\n\r\n const props = {\r\n lineCount: lines,\r\n value: textValue,\r\n disabled: !radioValue,\r\n onChange: this.handleTextChange,\r\n InputProps: {\r\n className: classes.inputText,\r\n // classes: {underline: classes.inputUnderline}\r\n },\r\n // onFocus: disableArrowKeyListeners,\r\n // onBlur: enableArrowKeyListeners, \r\n };\r\n\r\n return (\r\n \r\n );\r\n }\r\n\r\n displayDescription() {\r\n const description = this.getCurrentDescription();\r\n if (!description) return null;\r\n\r\n const { classes } = this.props;\r\n\r\n return (\r\n \r\n
\r\n
\r\n {description}\r\n \r\n
\r\n );\r\n }\r\n\r\n getCurrentDescription() {\r\n const { formData, value } = this.props;\r\n const { options } = formData;\r\n const currentValue = value ? value.radio : undefined;\r\n\r\n const description = options.reduce((text, option) => {\r\n const { value, description } = option;\r\n return value === currentValue ? description : text;\r\n }, null);\r\n\r\n return description;\r\n }\r\n\r\n getLabel(_value) {\r\n const { formData, value } = this.props;\r\n const { options } = formData;\r\n const currentValue = check.assigned(_value)\r\n ? _value\r\n : value\r\n ? value.radio\r\n : null;\r\n\r\n const label = options.reduce((text, option) => {\r\n const { value, label } = option;\r\n return value === currentValue ? label : text;\r\n }, null);\r\n\r\n return label;\r\n }\r\n\r\n displayConfirmation() {\r\n if (!check.nonEmptyString(this.confirmationText)){\r\n return null;\r\n }\r\n \r\n const { confirmation } = this.state;\r\n const props = {\r\n open: confirmation,\r\n onClose: this.handleConfirmation,\r\n label: this.confirmationText,\r\n values:{from: this.getLabel(), to:this.getLabel(this.confirmRadio)}\r\n };\r\n\r\n return ;\r\n }\r\n\r\n handleConfirmation(confirm) {\r\n if (confirm && check.assigned(this.confirmRadio)) {\r\n const { onChange } = this.props;\r\n\r\n onChange({\r\n radio: {\r\n target: { value: this.confirmRadio },\r\n },\r\n text: { target: { value: \"\" } },\r\n });\r\n }\r\n\r\n this.setState({ confirmation: null });\r\n this.confirmRadio = null;\r\n }\r\n\r\n render() {\r\n const { classes } = this.props;\r\n\r\n return (\r\n \r\n
{this.displayOptions()}
\r\n
{this.displayText()}
\r\n {this.displayConfirmation()}\r\n
\r\n );\r\n }\r\n\r\n isDisabled() {\r\n return this.context;\r\n }\r\n}\r\n\r\nMcqTextAnswerForm.contextType = DisableAnsweringContext;\r\n\r\n// TEMP\r\n\r\nconst styles = ({ palette, spacing }) => ({\r\n root: {},\r\n radioRoot: {\r\n color: palette.background.contrastText,\r\n padding: `0px ${spacing.unit}px ${spacing.unit}px`,\r\n },\r\n radioText: {\r\n color: palette.background.contrastText,\r\n marginBottom: spacing.unit * 3,\r\n },\r\n radioChecked: {\r\n color: palette.primary.main + \"!important\",\r\n },\r\n radioItem: {\r\n alignItems: \"flex-start\",\r\n },\r\n description: {\r\n margin: `${spacing.unit * 2}px 0px`,\r\n },\r\n wordCount:{\r\n marginTop: spacing.unit, \r\n },\r\n inputText: {\r\n\t\tcolor: `${palette.primary.main} !important`\r\n\t}, \r\n});\r\n\r\nMcqTextAnswerForm = withStyles(styles)(McqTextAnswerForm);\r\n\r\nMcqTextAnswerForm = withMessages(McqTextAnswerForm);\r\n\r\n// ---\r\n\r\nMcqTextAnswerForm.propTypes = {\r\n value: PropTypes.string,\r\n onChange: PropTypes.func.isRequired,\r\n formData: PropTypes.shape({\r\n options: PropTypes.arrayOf(\r\n PropTypes.shape({\r\n value: PropTypes.string.isRequired,\r\n label: PropTypes.string.isRequired,\r\n })\r\n ).isRequired,\r\n }).isRequired,\r\n};\r\n\r\nexport { McqTextAnswerForm };\r\n","const getOtherText = (mcqData) => mcqData.get(\"otherText\");\r\nconst getNumberOfLines = (mcqData) => mcqData.get(\"lines\");\r\nconst getShowWordCount = (mcqData) => mcqData.get(\"showWordCount\");\r\n\r\nexport { getOtherText, getNumberOfLines, getShowWordCount };\r\n","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\n// react\r\nimport { AnswerBox } from \"./answer-box\";\r\nimport { McqTextAnswerForm } from \"../answer_forms/mcq_text/mcq-text\";\r\n\r\n// redux (selectors)\r\nimport { getOptionsData } from \"redux/reducers/exam/content/questions/question/radios/selectors\";\r\nimport { getSelectedOptionData } from \"redux/reducers/exam/content/questions/question/radios/options/selectors\";\r\nimport {\r\n getOptionText,\r\n getOptionId,\r\n} from \"redux/reducers/exam/content/questions/question/radios/options/option/selectors\";\r\nimport { getOtherText, getNumberOfLines, getShowWordCount } from \"redux/reducers/exam/content/questions/question/radios-text/selectors\";\r\nimport { getLocalAnswer } from \"redux/reducers/exam/content/questions/question/selectors\";\r\n\r\nconst splitOptionText = (text) => {\r\n const pos = text.indexOf(\" \");\r\n\r\n return pos === -1\r\n ? { label: text, description: \"\" }\r\n : {\r\n label: text.substr(0, pos),\r\n description: removeStartBr(text.substring(pos + 6)),\r\n };\r\n};\r\n\r\nconst removeStartBr = (_text) => {\r\n const text = _text.trim();\r\n\r\n if (text.substring(0, 6) === \" \") {\r\n return removeStartBr(text.substring(6));\r\n }\r\n\r\n return text;\r\n};\r\n\r\nclass RadiosTextAnswerBox extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n this.setUpExtractionFunctions();\r\n this.setUpTransformationFunctions();\r\n }\r\n\r\n // extract information from questionData\r\n // --------------------------------------\r\n setUpExtractionFunctions() {\r\n this.extractInitialAnswer = () => undefined;\r\n\r\n this.extractResumedAnswer = () => {\r\n const resumedAnswer = {};\r\n\r\n const otherText = getOtherText(this.props.data);\r\n if (check.nonEmptyString(otherText)) {\r\n resumedAnswer.text = otherText;\r\n }\r\n\r\n const optionsData = getOptionsData(this.props.data);\r\n const selectedOptionData = getSelectedOptionData(optionsData);\r\n if (selectedOptionData) {\r\n resumedAnswer.radio = getOptionId(selectedOptionData);\r\n }\r\n\r\n return check.nonEmptyObject(resumedAnswer)?resumedAnswer:undefined;\r\n };\r\n\r\n this.extractFormData = () => {\r\n const lines = getNumberOfLines(this.props.data);\r\n const showWordCount = getShowWordCount(this.props.data)\r\n\r\n const options = getOptionsData(this.props.data)\r\n .map((optionData) => {\r\n const value = getOptionId(optionData).toString();\r\n const labelText = getOptionText(optionData);\r\n const { label, description } = splitOptionText(labelText);\r\n\r\n return { value, label, description };\r\n })\r\n .toArray();\r\n\r\n return { options, lines, showWordCount};\r\n };\r\n }\r\n\r\n // convert between answer values\r\n // --------------------------------------\r\n setUpTransformationFunctions() {\r\n this.getFormValue = (formAnswer) => {\r\n const formValue = { ...getLocalAnswer(this.props.data) };\r\n\r\n if (check.object(formAnswer)) {\r\n const { radio, text } = formAnswer;\r\n \r\n if (radio) formValue.radio = radio.target.value;\r\n if (text) formValue.text = text.target.value;\r\n }\r\n\r\n return formValue;\r\n };\r\n\r\n this.toFormValue = (localAnswer) => {\r\n if (localAnswer) {\r\n const { radio, text } = localAnswer;\r\n\r\n return { radio: radio.toString(), text };\r\n }\r\n\r\n return undefined;\r\n // return localAnswer ? localAnswer.toString() : undefined\r\n };\r\n\r\n this.toLocalAnswer = (formValue) => {\r\n if (formValue) {\r\n const { radio, text } = formValue;\r\n\r\n return { radio: parseInt(radio, 10), text };\r\n }\r\n\r\n return undefined;\r\n //return parseInt(formValue, 10)\r\n };\r\n\r\n this.toServerAnswer = (localAnswer) => {\r\n if (!localAnswer) {\r\n return localAnswer;\r\n }\r\n\r\n const { radio, text } = localAnswer;\r\n\r\n const selectedOption = this.getSelectedOption(radio);\r\n const optionId = getOptionId(selectedOption);\r\n const optionText = getOptionText(selectedOption);\r\n const options = [{ optionID: optionId, optionText }];\r\n\r\n return { options, answerText: optionText, otherText: text };\r\n };\r\n }\r\n\r\n render() {\r\n const answerBoxProps = {\r\n FormComponent: McqTextAnswerForm,\r\n questionData: this.props.data,\r\n toFormValue: this.toFormValue,\r\n getFormValue: this.getFormValue,\r\n toLocalAnswer: this.toLocalAnswer,\r\n toServerAnswer: this.toServerAnswer,\r\n extractFormData: this.extractFormData,\r\n extractInitialAnswer: this.extractInitialAnswer,\r\n extractResumedAnswer: this.extractResumedAnswer,\r\n noQuestionPanel: this.props.noQuestionPanel,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n getSelectedOption(localAnswer) {\r\n const optionsData = getOptionsData(this.props.data);\r\n return optionsData.find((optionData) => {\r\n return getOptionId(optionData) === localAnswer;\r\n });\r\n }\r\n}\r\n\r\nRadiosTextAnswerBox.propTypes = {\r\n data: PropTypes.object.isRequired,\r\n};\r\n\r\nexport { RadiosTextAnswerBox };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport ImmutablePropTypes from 'immutable-prop-types'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// redux\r\nimport {getType} from 'redux/reducers/exam/content/questions/question/selectors'\r\n\r\n\r\n// UnsupportedAnswerBox (not connected to store)\r\n// ----------------------------------------------------\r\n\r\nlet UnsupportedAnswerBox = ({questionType}) => (\r\n\t\r\n\t\t{`Unsupported questionType type: ${questionType}`}\r\n\t \r\n)\r\n\r\nUnsupportedAnswerBox.propTypes = {\r\n\tquestionType: PropTypes.string.isRequired\r\n}\r\n\r\n\r\n// UnsupportedAnswerBox (connected to store)\r\n// ----------------------------------------------------\r\n\r\nconst mapStoreToProps = (store, {data}) => ({\r\n\tquestionType: getType(data),\r\n\tdata: undefined\r\n});\r\n\r\nUnsupportedAnswerBox = connect(mapStoreToProps)(UnsupportedAnswerBox);\r\n\r\nUnsupportedAnswerBox.propTypes = {\r\n\tdata: ImmutablePropTypes.map.isRequired\r\n}\r\n\r\n\r\n// Export\r\n// ----------------------------------------------------\r\nexport {UnsupportedAnswerBox}","// npm\r\nimport React from \"react\";\r\n\r\n// react\r\nimport { AscxAnswerBox } from \"./answer_boxes/ascx\";\r\nimport { ComboAnswerBox } from \"./answer_boxes/combo\";\r\nimport { RadiosAnswerBox } from \"./answer_boxes/radios\";\r\nimport { ChecksAnswerBox } from \"./answer_boxes/checks\";\r\nimport { MatchAnswerBox } from \"./answer_boxes/match\";\r\nimport { TableAnswerBox } from \"./answer_boxes/table\";\r\nimport { BlanksAnswerBox } from \"./answer_boxes/blanks\";\r\nimport { MultiTextAnswerBox } from \"./answer_boxes/multi-text\";\r\nimport { IctAnswerBox } from \"./answer_boxes/ict\";\r\nimport { StaticTextAnswerBox } from \"./answer_boxes/static_text\";\r\nimport { FormAnswerBox } from \"./answer_boxes/form\";\r\nimport { RadiosTextAnswerBox } from \"./answer_boxes/radios-text\";\r\nimport { UnsupportedAnswerBox } from \"./answer_boxes/unsupported\";\r\n\r\n// redux\r\nimport {\r\n getType,\r\n getId,\r\n} from \"redux/reducers/exam/content/questions/question/selectors\";\r\n\r\n// constants\r\nimport * as QUESTION_TYPES from \"constants/question-types\";\r\n\r\nconst getAnswerBox = (questionData, noQuestionPanel=false) => {\r\n const props = { data: questionData, key: getId(questionData), noQuestionPanel };\r\n\r\n switch (getType(questionData)) {\r\n case QUESTION_TYPES.RADIOS:\r\n case QUESTION_TYPES.TRUE_OR_FALSE:\r\n return ;\r\n case QUESTION_TYPES.COMBO:\r\n return ;\r\n case QUESTION_TYPES.CHECKS:\r\n return ;\r\n case QUESTION_TYPES.MULTI_TEXT:\r\n return ;\r\n case QUESTION_TYPES.MATCH:\r\n return ;\r\n case QUESTION_TYPES.BLANKS:\r\n return ;\r\n case QUESTION_TYPES.TABLE:\r\n return ;\r\n case QUESTION_TYPES.ASCX:\r\n return ;\r\n case QUESTION_TYPES.ICT:\r\n return ;\r\n case QUESTION_TYPES.STATIC_TEXT:\r\n return ;\r\n case QUESTION_TYPES.FORM:\r\n return ;\r\n case QUESTION_TYPES.RADIOS_TEXT:\r\n return ;\r\n default:\r\n return ;\r\n }\r\n};\r\n\r\nexport { getAnswerBox };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport ImmutablePropTypes from 'immutable-prop-types'\r\n\r\n// react\r\nimport {Hide} from 'components/presentation/hide'\r\nimport {getAnswerBox} from '../../../exam/_layout/content/panel/answer_panel/get-answer-box'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getQuestionsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getQuestionDataValues} from 'redux/reducers/exam/content/questions/selectors'\r\n\r\n\r\n// ExamAnswerInitializer (not connected to store)\r\n// ------------------------------------------------------------\r\n\r\nclass ExamAnswerInitializer extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tthis.props.onFinish();\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tconst {questionDataList} = this.props;\r\n\t\treturn {questionDataList.map(data => getAnswerBox(data))} ;\r\n\t}\r\n}\r\n\r\nExamAnswerInitializer.propTypes = {\r\n\tonFinish: PropTypes.func.isRequired,\r\n\tquestionDataList: PropTypes.arrayOf(ImmutablePropTypes.map).isRequired\r\n}\r\n\r\n\r\n// ExamAnswerInitializer (connected to store)\r\n// ------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n\tconst contentData = getContentData(getExamData(store));\r\n\tconst questionsData = getQuestionsData(contentData);\r\n\treturn {\r\n\t\tquestionDataList: getQuestionDataValues(questionsData)\r\n\t}\r\n};\r\n\r\nExamAnswerInitializer = connect(mapStoreToProps)(ExamAnswerInitializer);\r\n\r\n\r\n// Export\r\n// ------------------------------------------------------------\r\nexport {ExamAnswerInitializer}","\nimport {Machine} from 'xstate'\nimport {XStateConfig} from './xstate-config'\n\n\n// #########################################\n// STATE NAMES\n// #########################################\n\nconst STATES = {\n\tCLEARING_PREVIOUS_EXAM: 'clearing_previous_exam',\n\tINITIALIZING: 'initializing',\n\tNETWORK_ERROR: 'exam_network_error',\n\tINITIALIZED: 'initialized'\n}\n\nconst INITIALIZING_STATES = {\n\tVALIDATING_SCHEDULE: 'validating_schedule',\n\tINPUTTING_PASSWORD: 'inputting_schedule_password',\n\tINITIALIZING_EXAM: 'initializing_exam',\n\tCHECKING_RESOURCES: 'checking_resources'\n}\n\nconst INITIALIZING_EXAM_STATES = {\n\tFETCHING_EXAM: 'fetching_exam',\n\tINITIALIZING_IMAGES: 'preloading_exam_images',\n\tINITIALIZING_RESOURCES: 'preloading_resources',\n\tINITIALIZING_ASCX_QUESTIONS: 'preloading_ascx_questions',\n\tINITIALIZING_ANSWERS: 'resuming_answers'\n}\n\nconst CHECKING_RESOURCES_STATES = {\n\tCHECKING_AUDIO: 'checking_audio',\n\tCHECKING_PDF: 'checking_pdf',\n\tCHECKING_INTEGRITY_ADVOCATE: 'checking_integrity_advocate',\n\tCHECKING_PROCTORIO: 'checking_proctorio'\n}\n\nconst VALIDATING_SCHEDULE_STATES = {\n\tINITIALIZING_SCHEDULE_DATA: 'initializing_schedule_data',\n\tCHECKING_SCHEDULE_COMPLETION: 'checking_if_schedule_completed',\n\tSCHEDULE_ALREADY_COMPLETED: 'schedule_already_complete',\n\tCHECKING_SCHEDULE_CAN_START: 'checking_if_schedule_can_start',\n\tSCHEDULE_CANT_START: 'schedule_cant_start',\n\tCHECKING_PASSWORD_REQUIREMENT: 'checking_if_schedule_needs_password'\n}\n\nconst INITIALIZING_SCHEDULE_DATA_STATES = {\n\tCHECKING_DATA: 'looking_for_schedule_data',\n\tFETCHING_DATA: 'fetching_schedule_data',\n\tDATA_NOT_FOUND: 'schedule_data_not_found'\n}\n\n\n// #########################################\n// EVENT NAMES\n// #########################################\n\nconst EVENTS = {\n\tERROR: 'error',\n\tSUCCESS: 'success',\n\tSCHEDULES_NOT_LOADED: 'schedule_not_loaded',\n\tSCHEDULE_NOT_FOUND: 'schedule_not_found',\n\tSCHEDULE_INCOMPLETE: 'schedule_incomplete',\n\tSCHEDULE_COMPLETE: 'schedule_complete',\n\tSCHEDULE_CANT_START: 'schedule_cant_start',\n\tSCHEDULE_CAN_START: 'schedule_can_start',\n\tPASSWORD_REQUIRED: 'password_required',\n\tPASSWORD_NOT_REQUIRED: 'password_not_required',\n\tPREVIOUS_EXAM_CLEARED: 'previous_exam_cleared'\n}\n\n\n// #########################################\n// INITIALIZING_SCHEDULE_DATA STATES\n// #########################################\n\nconst checkingScheduleData = new XStateConfig();\ncheckingScheduleData.addTransition(EVENTS.SCHEDULES_NOT_LOADED, INITIALIZING_SCHEDULE_DATA_STATES.FETCHING_DATA);\ncheckingScheduleData.addTransition(EVENTS.SCHEDULE_NOT_FOUND, INITIALIZING_SCHEDULE_DATA_STATES.DATA_NOT_FOUND);\n\nconst fetchingScheduleData = new XStateConfig();\nfetchingScheduleData.addTransition(EVENTS.SUCCESS, INITIALIZING_SCHEDULE_DATA_STATES.CHECKING_DATA);\n\nconst scheduleDataNotFound = new XStateConfig();\n\n\n// #########################################\n// VALIDATING_SCHEDULE STATES\n// #########################################\n\nconst initializingScheduleData = new XStateConfig();\ninitializingScheduleData.initialState = INITIALIZING_SCHEDULE_DATA_STATES.CHECKING_DATA;\ninitializingScheduleData.addState(INITIALIZING_SCHEDULE_DATA_STATES.CHECKING_DATA, checkingScheduleData);\ninitializingScheduleData.addState(INITIALIZING_SCHEDULE_DATA_STATES.FETCHING_DATA, fetchingScheduleData);\ninitializingScheduleData.addState(INITIALIZING_SCHEDULE_DATA_STATES.DATA_NOT_FOUND, scheduleDataNotFound);\ninitializingScheduleData.addTransition(EVENTS.SUCCESS, VALIDATING_SCHEDULE_STATES.CHECKING_SCHEDULE_COMPLETION);\n\nconst checkingScheduleCompletion = new XStateConfig();\ncheckingScheduleCompletion.addTransition(EVENTS.SCHEDULE_COMPLETE, VALIDATING_SCHEDULE_STATES.SCHEDULE_ALREADY_COMPLETED);\ncheckingScheduleCompletion.addTransition(EVENTS.SCHEDULE_INCOMPLETE, VALIDATING_SCHEDULE_STATES.CHECKING_SCHEDULE_CAN_START);\n\nconst scheduleAlreadyComplete = new XStateConfig();\n\nconst checkingScheduleCanStart = new XStateConfig();\ncheckingScheduleCanStart.addTransition(EVENTS.SCHEDULE_CANT_START, VALIDATING_SCHEDULE_STATES.SCHEDULE_CANT_START);\ncheckingScheduleCanStart.addTransition(EVENTS.SCHEDULE_CAN_START, VALIDATING_SCHEDULE_STATES.CHECKING_PASSWORD_REQUIREMENT);\n\nconst scheduleCantStart = new XStateConfig();\n\nconst checkingPasswordRequirement = new XStateConfig();\n\n\n// #########################################\n// INITIALIZING_EXAM STATES\n// #########################################\n\nconst fetchingExam = new XStateConfig();\nfetchingExam.addTransition(EVENTS.SUCCESS, INITIALIZING_EXAM_STATES.INITIALIZING_IMAGES);\n\nconst initializingExamImages = new XStateConfig();\ninitializingExamImages.addTransition(EVENTS.SUCCESS, INITIALIZING_EXAM_STATES.INITIALIZING_RESOURCES);\n\nconst initializingResources = new XStateConfig();\ninitializingResources.addTransition(EVENTS.SUCCESS, INITIALIZING_EXAM_STATES.INITIALIZING_ASCX_QUESTIONS);\n\nconst initializingAscxQuestions = new XStateConfig();\ninitializingAscxQuestions.addTransition(EVENTS.SUCCESS, INITIALIZING_EXAM_STATES.INITIALIZING_ANSWERS);\n\nconst initializingExamAnswers = new XStateConfig();\n\n// #########################################\n// CHECKING_RESOURCES STATES\n// #########################################\n\nconst checkingAudio = new XStateConfig();\ncheckingAudio.addTransition(EVENTS.SUCCESS, CHECKING_RESOURCES_STATES.CHECKING_PDF);\n\nconst checkingPdf = new XStateConfig();\ncheckingPdf.addTransition(EVENTS.SUCCESS, CHECKING_RESOURCES_STATES.CHECKING_INTEGRITY_ADVOCATE);\n\nconst checkingIntegrityAdvocate = new XStateConfig();\ncheckingIntegrityAdvocate.addTransition(EVENTS.SUCCESS, CHECKING_RESOURCES_STATES.CHECKING_PROCTORIO);\n\nconst checkingProctorio = new XStateConfig();\n\n\n// #########################################\n// INITIALIZING STATES\n// #########################################\n\nconst validatingSchedule = new XStateConfig();\nvalidatingSchedule.initialState = VALIDATING_SCHEDULE_STATES.INITIALIZING_SCHEDULE_DATA;\nvalidatingSchedule.addState(VALIDATING_SCHEDULE_STATES.INITIALIZING_SCHEDULE_DATA, initializingScheduleData);\nvalidatingSchedule.addState(VALIDATING_SCHEDULE_STATES.CHECKING_SCHEDULE_COMPLETION, checkingScheduleCompletion);\nvalidatingSchedule.addState(VALIDATING_SCHEDULE_STATES.SCHEDULE_ALREADY_COMPLETED, scheduleAlreadyComplete);\nvalidatingSchedule.addState(VALIDATING_SCHEDULE_STATES.CHECKING_SCHEDULE_CAN_START, checkingScheduleCanStart);\nvalidatingSchedule.addState(VALIDATING_SCHEDULE_STATES.SCHEDULE_CANT_START, scheduleCantStart);\nvalidatingSchedule.addState(VALIDATING_SCHEDULE_STATES.CHECKING_PASSWORD_REQUIREMENT, checkingPasswordRequirement);\nvalidatingSchedule.addTransition(EVENTS.PASSWORD_REQUIRED, INITIALIZING_STATES.INPUTTING_PASSWORD);\nvalidatingSchedule.addTransition(EVENTS.PASSWORD_NOT_REQUIRED, INITIALIZING_STATES.INITIALIZING_EXAM);\n\nconst inputtingPassword = new XStateConfig();\ninputtingPassword.addTransition(EVENTS.SUCCESS, INITIALIZING_STATES.INITIALIZING_EXAM);\n\nconst initializingExam = new XStateConfig();\ninitializingExam.initialState = INITIALIZING_EXAM_STATES.FETCHING_EXAM;\ninitializingExam.addState(INITIALIZING_EXAM_STATES.FETCHING_EXAM, fetchingExam);\ninitializingExam.addState(INITIALIZING_EXAM_STATES.INITIALIZING_IMAGES, initializingExamImages);\ninitializingExam.addState(INITIALIZING_EXAM_STATES.INITIALIZING_RESOURCES, initializingResources);\ninitializingExam.addState(INITIALIZING_EXAM_STATES.INITIALIZING_ASCX_QUESTIONS, initializingAscxQuestions);\ninitializingExam.addState(INITIALIZING_EXAM_STATES.INITIALIZING_ANSWERS, initializingExamAnswers);\ninitializingExam.addTransition(EVENTS.SUCCESS, INITIALIZING_STATES.CHECKING_RESOURCES);\n\nconst checkingResources = new XStateConfig();\ncheckingResources.initialState = CHECKING_RESOURCES_STATES.CHECKING_PDF;\ncheckingResources.addState(CHECKING_RESOURCES_STATES.CHECKING_AUDIO, checkingAudio);\ncheckingResources.addState(CHECKING_RESOURCES_STATES.CHECKING_PDF, checkingPdf);\ncheckingResources.addState(CHECKING_RESOURCES_STATES.CHECKING_INTEGRITY_ADVOCATE, checkingIntegrityAdvocate);\ncheckingResources.addState(CHECKING_RESOURCES_STATES.CHECKING_PROCTORIO, checkingProctorio);\n\n\n// #########################################\n// EXAM STATES\n// #########################################\n\nconst clearingPreviousExam = new XStateConfig();\nclearingPreviousExam.addTransition(EVENTS.PREVIOUS_EXAM_CLEARED, STATES.INITIALIZING);\n\nconst initializing = new XStateConfig();\ninitializing.initialState = INITIALIZING_STATES.VALIDATING_SCHEDULE;\ninitializing.addState(INITIALIZING_STATES.VALIDATING_SCHEDULE, validatingSchedule);\ninitializing.addState(INITIALIZING_STATES.INPUTTING_PASSWORD, inputtingPassword);\ninitializing.addState(INITIALIZING_STATES.INITIALIZING_EXAM, initializingExam);\ninitializing.addState(INITIALIZING_STATES.CHECKING_RESOURCES, checkingResources);\ninitializing.addTransition(EVENTS.SUCCESS, STATES.INITIALIZED);\ninitializing.addTransition(EVENTS.ERROR, STATES.NETWORK_ERROR);\n\nconst networkError = new XStateConfig();\nconst initialized = new XStateConfig();\n\n\n// #########################################\n// EXAM MACHINE\n// #########################################\n\nconst _examInit = new XStateConfig();\n_examInit.initialState = STATES.CLEARING_PREVIOUS_EXAM;\n_examInit.addState(STATES.CLEARING_PREVIOUS_EXAM, clearingPreviousExam);\n_examInit.addState(STATES.INITIALIZING, initializing);\n_examInit.addState(STATES.INITIALIZED, initialized);\n_examInit.addState(STATES.NETWORK_ERROR, networkError);\n\nconst machine = Machine(_examInit.toObject());\nmachine.id = \"Exam Init Machine\";\n\n\n// #########################################\n// EXPORT\n// #########################################\n\nconst examInit = {\n\tmachine,\n\tEVENTS: {...EVENTS},\n\tSTATES: {...STATES},\n\tINITIALIZING_STATES: {...INITIALIZING_STATES},\n\tINITIALIZING_EXAM_STATES: {...INITIALIZING_EXAM_STATES},\n\tCHECKING_RESOURCES_STATES: {...CHECKING_RESOURCES_STATES},\n\tVALIDATING_SCHEDULE_STATES: {...VALIDATING_SCHEDULE_STATES},\n\tINITIALIZING_SCHEDULE_DATA_STATES: {...INITIALIZING_SCHEDULE_DATA_STATES}\n}\n\nexport {examInit}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {ExamFetcher} from './exam-fetcher'\r\nimport {AscxInitializer} from './ascx-initializer'\r\nimport {ExamImagePreloader} from './image-preloader'\r\nimport {ExamResourceLoader} from './resource-loader'\r\nimport {ExamAnswerInitializer} from './answer-initializer'\r\n\r\n// machines\r\nimport {examInit} from 'machines/exam-init'\r\n\r\n\r\nconst {EVENTS, INITIALIZING_EXAM_STATES:STATES} = examInit;\r\n\r\n\r\nconst InitializingExamView = () =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t{{\r\n\t\t\t\t[STATES.FETCHING_EXAM]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\r\n\t\t\t\t[STATES.INITIALIZING_IMAGES]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\r\n\t\t\t\t[STATES.INITIALIZING_RESOURCES]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\r\n\t\t\t\t[STATES.INITIALIZING_ASCX_QUESTIONS]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\r\n\t\t\t\t[STATES.INITIALIZING_ANSWERS]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{({onFinish}) => }\r\n\t\t\t\t\t \r\n\t\t\t\t)\r\n\t\t\t}}\r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {InitializingExamView}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// utils\r\nimport {assessmentApi} from 'libs/api/interface/api-assessment'\r\n\r\n// redux (selectors)\r\nimport {getUserGuid} from 'redux/reducers/session/user/selectors'\r\nimport {getUserSessionData} from 'redux/reducers/session/selectors'\r\nimport {getSessionData, getSchedulesData} from 'redux/reducers/selectors'\r\nimport {getScheduleDataByExamGuid} from 'redux/reducers/schedules/selectors'\r\nimport {getScheduleType, getScheduleGuid, getExamGuid, getExamTypeGuid} from 'redux/reducers/schedules/schedule/selectors'\r\nimport { getSettingsData } from \"redux/reducers/selectors\"\r\nimport { getProctoringMode } from \"redux/reducers/settings/client/selectors\";\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport { getProctoringSessionID } from 'redux/reducers/settings/client/selectors'\r\n\r\n// redux (actions)\r\nimport {setCantStartMessage} from './actions'\r\n\r\n\r\n// CanStartChecker (not connected to store)\r\n// ------------------------------------------\r\n\r\nclass CanStartChecker extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.onSuccess = (response) => {\r\n\t\t\tconst parsedResponse = JSON.parse(response);\r\n\r\n\t\t\tif (parsedResponse.canStart) {\r\n\t\t\t\tthis.props.onSuccess();\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst {scheduleGuid} = this.props;\r\n\t\t\t\tthis.props.setCantStartMessage(scheduleGuid, parsedResponse.message);\r\n\t\t\t\tthis.props.onFail(); \r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tconst args = [\r\n\t\t\tthis.props.userGuid,\r\n\t\t\tthis.props.scheduleGuid,\r\n\t\t\tthis.props.scheduleType,\r\n\t\t\tthis.props.examGuid,\r\n\t\t\tthis.props.examTypeGuid,\r\n\t\t\tthis.props.proctoringMode,\r\n\t\t\tthis.props.proctoringSessionID\r\n\t\t]\r\n\r\n\t\tassessmentApi.canExamStart(...args)\r\n\t\t.then(this.onSuccess, this.props.onError);\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nCanStartChecker.propTypes = {\r\n\tuserGuid: PropTypes.string.isRequired,\r\n\tscheduleGuid: PropTypes.string.isRequired,\r\n\tscheduleType: PropTypes.string.isRequired,\r\n\texamGuid: PropTypes.string.isRequired,\r\n\texamTypeGuid: PropTypes.string.isRequired,\r\n\tsetCantStartMessage: PropTypes.func.isRequired,\r\n\tonSuccess: PropTypes.func.isRequired,\r\n\tonFail: PropTypes.func.isRequired,\r\n\tonError: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// CanStartChecker (connected to store)\r\n// ------------------------------------------\r\n\r\nconst mapStoreToProps = (store, {examGuid}) =>\r\n{ \r\n\tconst schedulesData = getSchedulesData(store);\r\n\tconst scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n\tconst settingsData = getSettingsData(store);\r\n\tconst userGuid = getUserGuid(getUserSessionData(getSessionData(store)));\r\n\tconst scheduleType = getScheduleType(scheduleData);\r\n\tconst scheduleGuid = getScheduleGuid(scheduleData);\r\n\tconst examTypeGuid = getExamTypeGuid(scheduleData);\r\n const proctoringMode = getProctoringMode(getClientSettingsData(settingsData));\r\n const proctoringSessionID = getProctoringSessionID(getClientSettingsData(getSettingsData(store)));\r\n\treturn {userGuid, scheduleGuid, scheduleType, examGuid, examTypeGuid, proctoringMode,proctoringSessionID}\r\n}\r\n\r\nconst mapDispatchToProps = (dispatch) =>\r\n{\r\n\treturn {\r\n\t\tsetCantStartMessage: (scheduleGuid, value) => {\r\n\t\t\tdispatch(setCantStartMessage(scheduleGuid, value));\r\n\t\t}\r\n\t}\r\n}\r\n\r\nCanStartChecker = connect(mapStoreToProps, mapDispatchToProps)(CanStartChecker);\r\n\r\n\r\n\r\nexport {CanStartChecker}","\r\n// redux (action-types)\r\nimport {SET_CANT_START_MESSAGE} from 'redux/reducers/schedules/schedule/action-types'\r\n\r\n\r\nconst setCantStartMessage = (scheduleGuid, message) => ({\r\n\ttype: SET_CANT_START_MESSAGE,\r\n\tguid: scheduleGuid,\r\n\tmessage\r\n});\r\n\r\n\r\nexport {setCantStartMessage}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// xams-utils\r\nimport {check} from '@xams-utils/check-types'\r\n\r\n// material-ui\r\nimport Dialog from '@material-ui/core/Dialog'\r\nimport DialogTitle from '@material-ui/core/DialogTitle'\r\nimport DialogActions from '@material-ui/core/DialogActions'\r\nimport DialogContent from '@material-ui/core/DialogContent'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\nimport DialogContentText from '@material-ui/core/DialogContentText'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n\r\nclass Popup extends React.Component {\r\n render() {\r\n const props = {\r\n classes: {\r\n paper: this.props.classes.paper,\r\n },\r\n onClose: this.props.onClose,\r\n disableBackdropClick: !this.props.canClickBackdrop,\r\n open: true,\r\n };\r\n\r\n return (\r\n \r\n {this.Title}\r\n {this.Content}\r\n {this.Actions}\r\n \r\n );\r\n }\r\n\r\n get Title() {\r\n const { title, classes } = this.props;\r\n if (!title) {\r\n return null;\r\n }\r\n return (\r\n \r\n \r\n {title}\r\n \r\n \r\n );\r\n }\r\n\r\n get Content() {\r\n const { content, classes } = this.props;\r\n if (!content) {\r\n return null;\r\n }\r\n\r\n const { text, node } = content;\r\n if (!check.string(text) && !node) {\r\n throw \"No popup content\";\r\n }\r\n\r\n return (\r\n \r\n {text ? (\r\n \r\n \r\n {text}\r\n \r\n \r\n ) : null}\r\n {node || null}\r\n \r\n );\r\n }\r\n\r\n get Actions() {\r\n const { buttons } = this.props;\r\n\r\n if (!buttons) {\r\n return null;\r\n }\r\n\r\n const _buttons = buttons.map((button, index) => {\r\n return check.assigned(button.key)\r\n ? button\r\n : { ...button, key: index };\r\n });\r\n\r\n return {_buttons} ;\r\n }\r\n}\r\n\r\nPopup.propTypes = {\r\n\ttitle: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),\r\n\tcontent: PropTypes.shape({\r\n\t\ttext: PropTypes.string, // used for string content (including nodes that render strings)\r\n\t\tnode: PropTypes.node\r\n\t}),\r\n\tonClose: PropTypes.func,\r\n\tbuttons: PropTypes.arrayOf(PropTypes.node),\r\n\tcanClickBackdrop: PropTypes.bool\r\n}\r\n\r\n\r\n\r\nconst styles = ({palette}) => ({\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t},\r\n\tpaper: {\r\n\t\tbackgroundColor: palette.background.light\r\n\t}\r\n})\r\n\r\nPopup = withStyles(styles)(Popup)\r\n\r\n\r\nexport {Popup}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {withRouter} from 'react-router-dom'\r\n\r\n// material-ui\r\nimport Button from '@material-ui/core/Button'\r\n\r\n// react\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst confirmMessageId = MESSAGE_IDS.GENERAL.CONFIRM;\r\n\r\n\r\n// CloseErrorButton (not connected to router/messages)\r\n// --------------------------------------------------------\r\nlet CloseErrorButton = ({history, messages}) =>\r\n{\r\n\tconst props = {\r\n\t\tsize: 'small',\r\n\t\tcolor: 'primary',\r\n\t\tvariant: 'outlined',\r\n\t\tdisableRipple: true,\r\n\t\tdisableFocusRipple: true,\r\n\t\tonClick: () => history.replace('/schedules')\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{messages[confirmMessageId]}\r\n\t\t \r\n\t);\r\n}\r\n\r\nCloseErrorButton.propTypes = {\r\n\thistory: PropTypes.object.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[confirmMessageId]: PropTypes.string.isRequired\r\n\t})\r\n}\r\n\r\n\r\n// CloseErrorButton (not connected to router/messages)\r\n// --------------------------------------------------------\r\nCloseErrorButton = withRouter(CloseErrorButton);\r\nCloseErrorButton = withMessages(CloseErrorButton);\r\n\r\n\r\n// Export\r\n// --------------------------------------------------------\r\nexport {CloseErrorButton}","// npm\r\nimport React from \"react\";\r\n\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getDemoMode } from \"redux/reducers/settings/client/selectors\";\r\n\r\n// react\r\nimport { Popup } from \"components/layout/popup\";\r\nimport { CloseErrorButton } from \"components/pages/exam/initialization/close-error-button\";\r\n\r\n// messages\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nlet ScheduleCompletedPopup = ({ messages, demoMode }) => {\r\n const props = {\r\n title: messages[MESSAGE_IDS.EXAM.CANT_START],\r\n content: { text: \"The assessment has already been completed\" },\r\n //buttons: [ ],\r\n canClickBackdrop: false,\r\n };\r\n\r\n if (!demoMode) {\r\n props.buttons = [ ];\r\n }\r\n\r\n return ;\r\n};\r\n\r\nScheduleCompletedPopup = withMessages(ScheduleCompletedPopup);\r\n\r\nconst mapStoreToProps = (store) => {\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n return {\r\n demoMode: getDemoMode(clientSettingsData),\r\n };\r\n};\r\n\r\nScheduleCompletedPopup = connect(mapStoreToProps)(ScheduleCompletedPopup);\r\n\r\nexport { ScheduleCompletedPopup };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {CloseErrorButton} from 'components/pages/exam/initialization/close-error-button'\r\n\r\n// redux (selectors)\r\nimport { getSchedulesData, getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getScheduleDataByExamGuid } from \"redux/reducers/schedules/selectors\";\r\nimport { getCantStartMessage } from \"redux/reducers/schedules/schedule/selectors\";\r\nimport { getProctoringMode } from \"redux/reducers/settings/client/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\n// ScheduleCantStartPopup (not connected to store)\r\n// ---------------------------------------------------------------\r\n\r\nlet ScheduleCantStartPopup = ({message, messages, proctoringMode}) =>\r\n{\r\n\tconst buttons = proctoringMode === \"1\" ? [] : [ ];\r\n\t\r\n\tconst props = {\r\n\t\ttitle: messages[MESSAGE_IDS.EXAM.CANT_START],\r\n\t\tbuttons,\r\n\t\tcontent: { text: message },\r\n\t\tcanClickBackdrop: false\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nScheduleCantStartPopup.propTypes = {\r\n\tmessage: PropTypes.string.isRequired\r\n}\r\n\r\n\r\n// ScheduleCantStartPopup (connected to store)\r\n// ---------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store, {examGuid}) => {\r\n\tconst schedulesData = getSchedulesData(store);\r\n\tconst scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n\r\n\tconst message = scheduleData?getCantStartMessage(scheduleData):\"\";\r\n\t\r\n\tconst settingsData = getSettingsData(store);\r\n const proctoringMode = getProctoringMode(getClientSettingsData(settingsData));\r\n\r\n\treturn { message, guid: undefined, proctoringMode }\r\n}\r\n\r\nScheduleCantStartPopup = connect(mapStoreToProps)(ScheduleCantStartPopup);\r\nScheduleCantStartPopup = withMessages(ScheduleCantStartPopup);\r\n\r\n\r\n// Export\r\n// ---------------------------------------------------------------\r\nexport {ScheduleCantStartPopup}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// redux\r\nimport {getSchedulesData} from 'redux/reducers/selectors'\r\nimport {getScheduleDataByExamGuid} from 'redux/reducers/schedules/selectors'\r\nimport {hasScheduleCompleted} from 'redux/reducers/schedules/schedule/selectors'\r\n\r\n\r\n\r\n// ScheduleCompletionChecker (not connected to store)\r\n// ------------------------------------------------\r\n\r\nclass ScheduleCompletionChecker extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tconst {schedule, onComplete, onIncomplete} = this.props;\r\n\t\tif (hasScheduleCompleted(schedule)) { onComplete(); }\r\n\t\telse { onIncomplete(); }\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nScheduleCompletionChecker.propTypes = {\r\n\tschedule: PropTypes.object.isRequired,\r\n\tonComplete: PropTypes.func.isRequired,\r\n\tonIncomplete: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// ScheduleCompletionChecker (connected to store)\r\n// ------------------------------------------------\r\n\r\nconst mapStoreToProps = (store, {examGuid}) => ({\r\n\tschedule: getScheduleDataByExamGuid(getSchedulesData(store))(examGuid)\r\n});\r\n\r\nScheduleCompletionChecker = connect(mapStoreToProps)(ScheduleCompletionChecker);\r\n\r\n\r\n// EXPORT\r\n// ------------------------------------------------\r\nexport {ScheduleCompletionChecker}","\r\n// redux (action-types)\r\nimport {SET_CURRENT_EXAM} from 'redux/reducers/exam/action-types'\r\nimport {ADD_SCHEDULE} from 'redux/reducers/schedules/action-types'\r\n\r\n\r\nconst addSchedule = (schedule) => ({\r\n\ttype: ADD_SCHEDULE,\r\n\t...schedule\r\n});\r\n\r\nconst setCurrentExam = (name, examGuid, scheduleGuid, practice) => ({\r\n\ttype: SET_CURRENT_EXAM,\r\n\tscheduleGuid,\r\n\texamGuid,\r\n\tname,\r\n\tpractice\r\n});\r\n\r\n\r\nexport {setCurrentExam, addSchedule}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// redux (actions)\r\nimport {setCurrentExam} from './actions'\r\n\r\n// redux (selectors)\r\nimport {getSchedulesData} from 'redux/reducers/selectors'\r\nimport {getScheduleDataByExamGuid, getScheduleCount} from 'redux/reducers/schedules/selectors'\r\nimport {getExamName, getScheduleGuid, isSchedulePractice} from 'redux/reducers/schedules/schedule/selectors'\r\n\r\n\r\n\r\n// ScheduleDataChecker (not connected to store)\r\n// ----------------------------------------------\r\n\r\nclass ScheduleDataChecker extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tconst {scheduleCount, schedule} = this.props;\r\n\r\n\t\tif (scheduleCount === 0) {\r\n\t\t\tthis.props.onSchedulesNotLoaded();\r\n\t\t}\r\n\t\telse if (!schedule) {\r\n\t\t\tthis.props.onScheduleNotFound();\r\n\t\t}\r\n\t\telse {\r\n\t\t\tconst examName = getExamName(schedule);\r\n\t\t\tconst scheduleGuid = getScheduleGuid(schedule);\r\n\t\t\tconst practiceExam = isSchedulePractice(schedule);\r\n\t\t\tthis.props.onSuccess(examName, scheduleGuid, practiceExam);\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nScheduleDataChecker.propTypes = {\r\n\tschedule: PropTypes.object,\r\n\tonSuccess: PropTypes.func.isRequired,\r\n\tonScheduleNotFound: PropTypes.func.isRequired,\r\n\tonSchedulesNotLoaded: PropTypes.func.isRequired,\r\n\tscheduleCount: PropTypes.number.isRequired\r\n}\r\n\r\n\r\n// ScheduleDataChecker (not connected to store)\r\n// ----------------------------------------------\r\n\r\nconst mapStoreToProps = (store, {examGuid}) =>\r\n{\r\n\tconst schedulesData = getSchedulesData(store);\r\n\r\n\treturn {\r\n\t\tschedule: getScheduleDataByExamGuid(schedulesData)(examGuid),\r\n\t\tscheduleCount: getScheduleCount(schedulesData)\r\n\t}\r\n}\r\n\r\nconst mapDispatchToProps = (dispatch, ownProps) => ({\r\n\tonSuccess: (examName, scheduleGuid, practice) => {\r\n\t\tconst {examGuid, onSuccess} = ownProps;\r\n\t\tdispatch(setCurrentExam(examName, examGuid, scheduleGuid, practice));\r\n\t\tonSuccess();\r\n\t}\r\n});\r\n\r\nScheduleDataChecker = connect(mapStoreToProps, mapDispatchToProps)(ScheduleDataChecker);\r\n\r\n\r\n// ScheduleDataChecker (not connected to store)\r\n// ----------------------------------------------\r\nexport {ScheduleDataChecker}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// xams-utils\r\nimport {check} from '@xams-utils/check-types'\r\n\r\n// redux\r\nimport {addSchedule} from './actions'\r\nimport {getUserSessionData} from 'redux/reducers/session/selectors'\r\nimport {getUserGuid} from 'redux/reducers/session/user/selectors'\r\nimport {getSessionData} from 'redux/reducers/selectors'\r\nimport {getSettingsData} from 'redux/reducers/selectors'\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getProctoringMode} from 'redux/reducers/settings/client/selectors'\r\nimport {getProctoringType} from 'redux/reducers/settings/client/selectors'\r\n\r\n// utils\r\nimport {COOKIE_NAMES, cookies} from 'utils/cookies'\r\nimport {assessmentApi} from 'libs/api/interface/api-assessment'\r\nimport {sessionStorageApi, KEYS} from 'libs/browser_storage/session-storage-api'\r\n\r\n\r\n\r\n// ScheduleDataFetcher (not connected to store)\r\n// --------------------------------------------\r\n\r\nclass ScheduleDataFetcher extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tconst schedule = this.getScheduleFromQueryString() ||\r\n\t\t\t\t\t\t\t\t\t\t this.getScheduleFromSessionStorage();\r\n\r\n\t\tif (schedule) {\r\n\t\t\tthis.props.saveSchedules([schedule]);\r\n\t\t\tthis.props.onSuccess();\r\n\t\t}\r\n\t\telse {\r\n\t\t\tconst onSuccess = (response) => {\r\n\t\t\t\tconst parsedResponse = JSON.parse(response);\r\n\t\t\t\tthis.props.saveSchedules(parsedResponse);\r\n\t\t\t\tthis.props.onSuccess();\r\n\t\t\t}\r\n\r\n\t\t\tassessmentApi.getExamSchedule(this.props.userGuid, this.props.proctoringMode)\r\n\t\t\t.then(onSuccess, this.props.onFail);\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n\r\n\tgetScheduleFromQueryString()\r\n\t{\r\n\t\tconst urlParameters = new URLSearchParams(window.location.search);\r\n\t\tconst externalSessionData = urlParameters.get('x');\r\n\t\tif (!externalSessionData) { return; }\r\n\r\n\t\tconst parsedSessionData = JSON.parse(externalSessionData);\r\n\t\tconst {schedule, exam} = parsedSessionData;\r\n\r\n\t\tsessionStorageApi.saveDataTo(KEYS.EXTERNAL_EXAM, exam);\r\n\t\tsessionStorageApi.saveDataTo(KEYS.EXTERNAL_SCHEDULE, schedule || {});\r\n\r\n\t\treturn this.getScheduleFromExternalData(parsedSessionData);\r\n\t}\r\n\r\n\tgetScheduleFromSessionStorage()\r\n\t{\r\n\t\tconst externalScheduleData = sessionStorageApi.retrieveDataFrom(KEYS.EXTERNAL_SCHEDULE);\r\n\t\tconst externalExamData = sessionStorageApi.retrieveDataFrom(KEYS.EXTERNAL_EXAM);\r\n\t\tif (check.emptyObject(externalExamData)) { return; }\r\n\r\n\t\treturn this.getScheduleFromExternalData({\r\n\t\t\tschedule: externalScheduleData,\r\n\t\t\texam: externalExamData\r\n\t\t});\r\n\t}\r\n\r\n\tgetScheduleFromExternalData(externalDataObject) // externalDataObject may be parsed from querystring or sessionstorage\r\n\t{\r\n\t\tlet {schedule, exam} = externalDataObject;\r\n\t\tif (schedule === null) { schedule = {}; }\r\n\r\n\t\treturn {\r\n\t\t\tguid: schedule.guid || null,\r\n\t\t\tscheduleType: schedule.scheduleType || null,\r\n\t\t\texamName: schedule.examName,\r\n\t\t\tobjectGUID: exam.guid,\r\n\t\t\tobjectTypeGUID: exam.objectTypeGuid,\r\n\t\t\tversion: exam.version,\r\n\t\t\tproctorProvider: schedule.proctorProvider || null,\r\n\t\t\tproctoringSessionID: schedule.proctoringSessionID || null,\r\n\t\t\tobjectID: schedule.objectID || null,\r\n\t\t\tqualificationID: schedule.qualificationID || null,\t\t\t\r\n\t\t\trequiresPassword: this.getRequiresPassword(schedule),\r\n\t\t\tpracticeExtraTime: schedule.extraTime,\r\n\t\t}\r\n\t}\r\n\r\n\tgetRequiresPassword(schedule)\r\n\t{\r\n\t\t//debugger\r\n\t\tif (schedule.requiresPassword == null || schedule.requiresPassword == false) return false; //no password\r\n\t\t//if proctored and record and review, password not required\r\n\t\tif (this.props.proctoringMode != null && this.props.proctoringType == 1) return false;\r\n\t\treturn schedule.requiresPassword;\r\n\t}\r\n}\r\n\r\nScheduleDataFetcher.propTypes = {\r\n\tuserGuid: PropTypes.string.isRequired,\r\n\tsaveSchedules: PropTypes.func.isRequired,\r\n\tonSuccess: PropTypes.func.isRequired,\r\n\tonFail: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// ScheduleDataFetcher (connected to store)\r\n// --------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\tuserGuid: getUserGuid(getUserSessionData(getSessionData(store))),\r\n\tproctoringMode: getProctoringMode(getClientSettingsData(getSettingsData(store))),\r\n\tproctoringType: getProctoringType(getClientSettingsData(getSettingsData(store)))\r\n});\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tsaveSchedules: (response) => {\r\n\t\tresponse.forEach(item => {\r\n\t\t\tdispatch(addSchedule(item));\r\n\t\t});\r\n\t}\r\n});\r\n\r\nScheduleDataFetcher = connect(mapStoreToProps, mapDispatchToProps)(ScheduleDataFetcher);\r\n\r\n\r\nexport {ScheduleDataFetcher}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {CloseErrorButton} from 'components/pages/exam/initialization/close-error-button'\r\n\r\n\r\nconst ScheduleNotFoundPopup = () =>\r\n{\r\n\tconst props = {\r\n\t\ttitle: 'Schedule not found',\r\n\t\tbuttons: [ ],\r\n\t\tcanClickBackdrop: false\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\n\r\nexport {ScheduleNotFoundPopup}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {ScheduleDataChecker} from './checker'\r\nimport {ScheduleDataFetcher} from './fetcher'\r\nimport {ScheduleNotFoundPopup} from './schedule-not-found-popup'\r\n\r\n// machines\r\nimport {examInit} from 'machines/exam-init'\r\n\r\n\r\nconst {EVENTS, INITIALIZING_SCHEDULE_DATA_STATES:STATES} = examInit;\r\n\r\n\r\nconst InitializingScheduleView = (examGuid) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t{{\r\n\t\t\t\t[STATES.CHECKING_DATA]: () => {\r\n\t\t\t\t\tconst controlProps = {\r\n\t\t\t\t\t\tonSuccess: EVENTS.SUCCESS,\r\n\t\t\t\t\t\tonScheduleNotFound: EVENTS.SCHEDULE_NOT_FOUND,\r\n\t\t\t\t\t\tonSchedulesNotLoaded: EVENTS.SCHEDULES_NOT_LOADED\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t)\r\n\t\t\t\t},\r\n\t\t\t\t[STATES.FETCHING_DATA]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\r\n\t\t\t\t[STATES.DATA_NOT_FOUND]: () => (\r\n\t\t\t\t\t \r\n\t\t\t\t)\r\n\t\t\t}}\r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {InitializingScheduleView}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// redux\r\nimport {getSchedulesData} from 'redux/reducers/selectors'\r\nimport {getScheduleDataByExamGuid} from 'redux/reducers/schedules/selectors'\r\nimport {requiresSchedulePassword} from 'redux/reducers/schedules/schedule/selectors'\r\n\r\n\r\n// PasswordRequirementChecker (not connected to store)\r\n// ----------------------------------------------------\r\n\r\nclass PasswordRequirementChecker extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tconst {schedule, onRequired} = this.props;\r\n\t\tif (requiresSchedulePassword(schedule)) { onRequired(); }\r\n\t\telse { this.props.onNotRequired(); }\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nPasswordRequirementChecker.propTypes = {\r\n\tschedule: PropTypes.object.isRequired,\r\n\tonRequired: PropTypes.func.isRequired,\r\n\tonNotRequired: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// PasswordRequirementChecker (connected to store)\r\n// ----------------------------------------------------\r\n\r\nconst mapStoreToProps = (store, {examGuid}) => ({\r\n\tschedule: getScheduleDataByExamGuid(getSchedulesData(store))(examGuid)\r\n});\r\n\r\nPasswordRequirementChecker = connect(mapStoreToProps)(PasswordRequirementChecker);\r\n\r\n\r\nexport {PasswordRequirementChecker}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {CanStartChecker} from './can-start-checker'\r\nimport {ScheduleCompletedPopup} from './completed-popup'\r\nimport {ScheduleCantStartPopup} from './cant-start-popup'\r\nimport {ScheduleCompletionChecker} from './completion-checker'\r\nimport {InitializingScheduleView} from './initializing_schedule/view'\r\nimport {PasswordRequirementChecker} from './password-requirement-checker'\r\n\r\n// machines\r\nimport {examInit} from 'machines/exam-init'\r\n\r\n\r\nconst {EVENTS, VALIDATING_SCHEDULE_STATES:STATES} = examInit;\r\n\r\n\r\nconst ValidatingScheduleView = (examGuid) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t{{\r\n\t\t\t\t[STATES.INITIALIZING_SCHEDULE_DATA]: () => (\r\n\t\t\t\t\tInitializingScheduleView(examGuid)\r\n\t\t\t\t),\r\n\t\t\t\t[STATES.CHECKING_SCHEDULE_COMPLETION]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\r\n\t\t\t\t[STATES.SCHEDULE_ALREADY_COMPLETED]: () => (\r\n\t\t\t\t\t \r\n\t\t\t\t),\r\n\t\t\t\t[STATES.CHECKING_SCHEDULE_CAN_START]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\r\n\t\t\t\t[STATES.SCHEDULE_CANT_START]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t),\r\n\t\t\t\t[STATES.CHECKING_PASSWORD_REQUIREMENT]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t)\r\n\t\t\t}}\r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {ValidatingScheduleView}","import { detect } from \"detect-browser\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nconst isBrowserSupported = (supportedBrowser, currentBrowser) => {\r\n const { name, version } = currentBrowser;\r\n\r\n if (\r\n !check.nonEmptyObject(supportedBrowser) ||\r\n !check.nonEmptyString(name)\r\n ) {\r\n return false;\r\n }\r\n\r\n if (!browserNameMatch(supportedBrowser, currentBrowser)) {\r\n return false;\r\n }\r\n\r\n const supportedBrowserVersion = check.assigned(supportedBrowser.versions)\r\n ? supportedBrowser.versions\r\n : \"\";\r\n\r\n // console.log(\"supportedBrowserVersion\", supportedBrowserVersion);\r\n\r\n if (check.string(supportedBrowserVersion)) {\r\n if (supportedBrowserVersion.trim() === \"\") {\r\n return true;\r\n }\r\n return compareVersion(version, supportedBrowserVersion);\r\n } else if (check.nonEmptyArray(supportedBrowserVersion)) {\r\n const comparisons = supportedBrowserVersion.reduce(\r\n (a, item) => {\r\n const { comparison } = getComparisonAndVersion(item);\r\n\r\n if (comparison === \"=\") a[0].push(item);\r\n else a[1].push(item);\r\n\r\n return a;\r\n },\r\n [[], []]\r\n );\r\n\r\n const checkEquals =\r\n comparisons[0].length > 0\r\n ? comparisons[0].some((item) => compareVersion(version, item))\r\n : false;\r\n const checkNotEquals =\r\n comparisons[1].length > 0\r\n ? comparisons[1].every((item) => compareVersion(version, item))\r\n : false;\r\n\r\n return checkEquals || checkNotEquals;\r\n }\r\n};\r\n\r\nconst getComparisonAndVersion = (item) => {\r\n const firstChar = item.charAt(0);\r\n const isFirstNumber = firstChar >= \"0\" && firstChar <= \"9\";\r\n const comparison = isFirstNumber ? \"=\" : firstChar;\r\n const version = getVersionBits(isFirstNumber ? item : item.substring(1));\r\n\r\n return { version, comparison };\r\n};\r\n\r\nconst compareVersion = (currentVersion, supported) => {\r\n if (supported.trim() === \"\") {\r\n return true;\r\n }\r\n const { version: supportedVersion, comparison } =\r\n getComparisonAndVersion(supported);\r\n\r\n const size =\r\n currentVersion.length > supportedVersion.length\r\n ? supportedVersion.length\r\n : currentVersion.length;\r\n\r\n for (let i = 0; i < size; i++) {\r\n const last = i === size - 1;\r\n if (\r\n !check.assigned(currentVersion[i]) ||\r\n !check.assigned(supportedVersion[i])\r\n ) {\r\n return true;\r\n }\r\n if (comparison === \"=\" && currentVersion[i] !== supportedVersion[i]) {\r\n return false;\r\n } else if (comparison === \">\") {\r\n if (last || currentVersion[i] !== supportedVersion[i]) {\r\n return currentVersion[i] > supportedVersion[i];\r\n }\r\n } else if (\r\n comparison === \"<\" &&\r\n currentVersion[i] >= supportedVersion[i]\r\n ) {\r\n return false;\r\n } else if (\r\n comparison === \"!\" &&\r\n currentVersion[i] !== supportedVersion[i]\r\n ) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n};\r\n\r\nconst getSupportedVersionText = (supportedBrowser) => {\r\n const versions = supportedBrowser.versions;\r\n\r\n if (check.string(versions)) {\r\n return `version ${getVersionText(versions)}`;\r\n } else if (check.nonEmptyArray(versions)) {\r\n //console.log(\"Versions\", versions);\r\n return `version ${versions.reduce((text, version) => {\r\n if (!check.assigned(text)) {\r\n const { comparison } = getComparisonAndVersion(version);\r\n if (comparison === \">\") {\r\n return getVersionText(version);\r\n }\r\n }\r\n return text;\r\n }, null)}`;\r\n }\r\n};\r\n\r\nconst getVersionText = (versionText) => {\r\n const { version, comparison } = getComparisonAndVersion(versionText);\r\n if (comparison === \"=\") return displayVersion(version);\r\n if (comparison === \">\")\r\n return `${displayVersion(nextVersion(version))} or greater`;\r\n return null;\r\n};\r\n\r\nconst displayVersion = (version) => {\r\n const _version = check.string(version) ? getVersionBits(version) : version;\r\n\r\n return removeTrailingZeros(_version).join(\".\");\r\n};\r\n\r\nconst removeTrailingZeros = (version) => {\r\n let _version = [];\r\n for (let i = version.length - 1; i >= 0; i--) {\r\n if (_version.length > 0 || version[i]) {\r\n _version.unshift(version[i]);\r\n }\r\n }\r\n\r\n return _version;\r\n};\r\n\r\nconst nextVersion = (version) => {\r\n for (let i = 2; i >= 0; i--) {\r\n if (check.assigned(version[i])) {\r\n version[i]++;\r\n break;\r\n }\r\n }\r\n\r\n return version;\r\n};\r\n\r\nconst getVersionBits = (versionString) => {\r\n const versions = check.nonEmptyString(versionString)\r\n ? versionString.split(\".\").map((version) => parseInt(version, 10))\r\n : [];\r\n\r\n return versions;\r\n};\r\n\r\nconst browserNameMatch = (supportedBrowser, currentBrowser) => {\r\n const { name } = currentBrowser;\r\n\r\n const supportedBrowserName = check.nonEmptyString(supportedBrowser.name)\r\n ? supportedBrowser.name.toLowerCase()\r\n : null;\r\n\r\n return supportedBrowserName === name.toLowerCase();\r\n};\r\n\r\nconst getOtherSupportedBrowsers = (supportedBrowsers, supportedBrowser, os) => {\r\n const displayOthers = check.nonEmptyObject(supportedBrowser)\r\n ? supportedBrowser.displayOthers\r\n : true;\r\n\r\n if (check.assigned(displayOthers) && !displayOthers) {\r\n return null;\r\n }\r\n const otherBrowsers = supportedBrowsers.filter((browser) => {\r\n const { displayInOthers } = browser;\r\n if (check.assigned(displayInOthers) && !displayInOthers) {\r\n return false;\r\n }\r\n\r\n if (\r\n (check.nonEmptyObject(supportedBrowser) &&\r\n // browser.name === supportedBrowser.name) ||\r\n browserNamesTheSame(browser, supportedBrowser)) ||\r\n !check.nonEmptyArray(browser.os)\r\n ) {\r\n return false;\r\n }\r\n\r\n return browser.os.some(\r\n (browserOS) => browserOS.toLowerCase() === os.toLowerCase()\r\n );\r\n });\r\n\r\n return otherBrowsers;\r\n};\r\n\r\nconst browserNamesTheSame = (browser, supportedBrowser) => {\r\n if (browser.name === supportedBrowser.name) {\r\n return true;\r\n }\r\n if (\r\n check.nonEmptyString(browser.display) &&\r\n check.nonEmptyString(supportedBrowser.display)\r\n ) {\r\n if (browser.display === supportedBrowser.display) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n};\r\n\r\nconst getBrowserName = (browser) => {\r\n const { name, display } = browser;\r\n\r\n return check.nonEmptyString(display) ? display : name;\r\n};\r\n\r\nconst populateMessage = (message, variables) => {\r\n for (let i = 0; i < variables.length; i++) {\r\n message = message.replace(`[${variables[i][0]}]`, variables[i][1]);\r\n }\r\n return message;\r\n};\r\n\r\nconst getCurrentBrowser = () => {\r\n const { name, version, os } = detect();\r\n const browser = { name, version, os };\r\n\r\n const sebBrowser = getSebBrowser();\r\n\r\n if (check.nonEmptyObject(sebBrowser)) {\r\n browser.version = sebBrowser.version;\r\n browser.name = sebBrowser.name;\r\n }\r\n\r\n // browser.name = \"chrome\";\r\n // browser.version = \"79.0.0\";\r\n // browser.os = \"Chrome OS\";\r\n\r\n return browser;\r\n};\r\n\r\nconst getSebBrowser = () => {\r\n const sebBrowser = {};\r\n const sebCheck = \"seb/\";\r\n const userAgent = navigator.userAgent;\r\n\r\n if (check.nonEmptyString(userAgent)) {\r\n const a = userAgent.split(\" \");\r\n\r\n for (let i = 0; i < a.length; i++) {\r\n const s = a[i].trim().toLowerCase();\r\n if (check.nonEmptyString(s) && s.length >= sebCheck.length) {\r\n if (\r\n sebCheck === s.substring(0, sebCheck.length).toLowerCase()\r\n ) {\r\n const v = s.split(\"/\");\r\n\r\n if (check.nonEmptyArray(v) && v.length === 2) {\r\n sebBrowser.version = v[1].trim();\r\n sebBrowser.name = \"SEB\";\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n return sebBrowser;\r\n};\r\n\r\nconst isOSExempt = (os) => {\r\n const oSExempt = isCromeOS(os);\r\n\r\n return oSExempt;\r\n};\r\n\r\nconst isCromeOS = (os) => {\r\n return check.nonEmptyString(os) && os.toLowerCase() === \"chrome os\";\r\n};\r\n\r\nconst checkPdfBrowserSupport = (supportedBrowsers) => {\r\n const { name, version, os } = getCurrentBrowser();\r\n\r\n if (isOSExempt(os)) {\r\n return { supported: true };\r\n }\r\n\r\n const currentVersion = getVersionBits(version);\r\n const currentBrowser = { name, version: currentVersion, os };\r\n\r\n const isSupported = supportedBrowsers.some((supportedBrowser) =>\r\n isBrowserSupported(supportedBrowser, currentBrowser)\r\n );\r\n\r\n if (isSupported) {\r\n return { supported: true };\r\n }\r\n\r\n const supportedBrowser = supportedBrowsers.find((supportedBrowser) => {\r\n return browserNameMatch(supportedBrowser, currentBrowser);\r\n });\r\n\r\n if (check.nonEmptyObject(supportedBrowser)) {\r\n if (!check.nonEmptyArray(supportedBrowser.versions)) {\r\n return { notSupported: true };\r\n }\r\n\r\n return { versionNotSupported: supportedBrowser };\r\n } else {\r\n return { notSupported: true };\r\n }\r\n};\r\n\r\nconst useOldPdfViewer=()=>{\r\n\tconst { version, os } = getCurrentBrowser();\r\n\r\n\tif (isCromeOS(os)){\r\n\t\tconst currentVersion = getVersionBits(version);\r\n\t\tconst oldVersion = \"<80\"\r\n\t\tconst useOldVersion=compareVersion(currentVersion, oldVersion);\r\n\r\n\t\treturn useOldVersion;\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\r\nexport {\r\n checkPdfBrowserSupport,\r\n getCurrentBrowser,\r\n displayVersion,\r\n getBrowserName,\r\n getOtherSupportedBrowsers,\r\n getSupportedVersionText,\r\n populateMessage,\r\n useOldPdfViewer\r\n};\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Paper from '@material-ui/core/Paper'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n\r\n// TextPanel (not connected to styles)\r\n// --------------------------------------------------------------------\r\n\r\nlet TextPanel = ({classes, text, style}) => (\r\n\t\r\n\t\t{text} \r\n\t \r\n)\r\n\r\nTextPanel.defaultProps = {\r\n\tstyle: {}\r\n}\r\n\r\nTextPanel.propTypes = {\r\n\ttext: PropTypes.string.isRequired,\r\n\tstyle: PropTypes.object.isRequired,\r\n\tclasses: PropTypes.object.isRequired\r\n}\r\n\r\n\r\n// TextPanel (connected to styles)\r\n// --------------------------------------------------------------------\r\n\r\nconst styles = (theme) => ({\r\n\tpanel: {\r\n\t\tflexGrow: 1,\r\n\t\tmaxWidth: 800,\r\n\t\tboxSizing: 'border-box',\r\n\t\tpadding: theme.spacing.unit,\r\n\t\tmargin: theme.spacing.unit,\r\n\t\tbackgroundColor: theme.palette.background.light,\r\n\t},\r\n\ttext: {\r\n\t\tcolor: theme.palette.background.contrastText\r\n\t}\r\n});\r\n\r\nTextPanel = withStyles(styles)(TextPanel);\r\n\r\n\r\n// Export\r\n// --------------------------------------------------------------------\r\nexport {TextPanel}","import React, { Component } from \"react\";\r\nimport {withRouter} from 'react-router-dom'\r\n\r\nimport Button from \"@material-ui/core/Button\";\r\nimport Divider from '@material-ui/core/Divider'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n\r\nconst styles = ({palette, spacing }) => ({\r\n\tbuttons: {\r\n marginTop: spacing.unit * 2,\r\n\t\ttextAlign: 'right'\r\n\t},\r\n\tbutton: {\r\n\t\tbackgroundColor: palette.primary.main + \"!important\",\r\n\t\tcolor: palette.primary.contrastText + \"!important\"\r\n\t},\r\n\tbuttonDisabled: {\r\n\t\tbackgroundColor: palette.background.light + \"!important\",\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t},\r\n\tsubmit: {\r\n\t\tmarginLeft: spacing.unit\r\n\t},\r\n\tinputUnderline: {\r\n\t\t'&:before': {\r\n\t\t\tborderBottom: `1px solid ${palette.background.contrastText} !important`\r\n\t\t}\r\n\t}\r\n});\r\n\r\n\r\nclass PdfCheckButtons extends Component {\r\n state = {};\r\n\r\n returnToSchedulesPage(props) {\r\n this.props.history.replace(\"/schedules\");\r\n }\r\n\r\n render() {\r\n const {classes}=this.props;\r\n\r\n return (\r\n \r\n \r\n {this.ContinueButton}\r\n {this.CancelButton}\r\n
\r\n \r\n );\r\n }\r\n\r\n get ContinueButton() {\r\n const { classes, onContinue, canContinue, messages } = this.props;\r\n\r\n if (!canContinue){\r\n return null;\r\n }\r\n\r\n const props = {\r\n variant: \"contained\",\r\n color: \"primary\",\r\n classes: {\r\n root: `${classes.submit} ${classes.button}`,\r\n disabled: classes.buttonDisabled,\r\n },\r\n disabled: !!this.props.disableSubmit,\r\n onClick: () => onContinue(),\r\n };\r\n\r\n return {messages.continue} ;\r\n }\r\n\r\n get CancelButton() {\r\n //jd 4/10/2020 - suppress cancel button if proctored and single exam\r\n if (this.props.proctoringMode == 1) return;\r\n\r\n const { classes, messages } = this.props;\r\n\r\n const props = {\r\n variant: \"contained\",\r\n color: \"secondary\",\r\n classes: {\r\n root: classes.submit,\r\n disabled: classes.buttonDisabled,\r\n },\r\n onClick: () => this.returnToSchedulesPage(props),\r\n };\r\n\r\n return {messages.cancel} ;\r\n }\r\n}\r\n\r\nPdfCheckButtons = withRouter(PdfCheckButtons);\r\nPdfCheckButtons = withStyles(styles)(PdfCheckButtons);\r\n\r\nexport { PdfCheckButtons };\r\n","import React from 'react';\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { getBrowserIcon } from \"react-browser-icons\";\r\nimport HelpOutlineIcon from \"@material-ui/icons//HelpOutline\";\r\n\r\nconst PdfCheckBrowserIcon = (props) => {\r\n const {browser, size:_size}=props;\r\n const size = check.integer(_size)?_size:42;\r\n const name=check.nonEmptyObject(browser)?browser.name:\"\"\r\n let browserName = \"\";\r\n \r\n switch (name.toLowerCase()) {\r\n case \"chrome\":\r\n browserName = \"Chrome\";\r\n break;\r\n case \"safari\":\r\n browserName = \"Safari\";\r\n break;\r\n case \"firefox\":\r\n browserName = \"Firefox\";\r\n break;\r\n case \"edge-chromium\":\r\n browserName = \"Chromium\";\r\n break;\r\n case \"edge\":\r\n browserName = \"Edge\";\r\n break; \r\n }\r\n\r\n const icon =\r\n browserName === \"\" ? (\r\n \r\n ) : (\r\n getBrowserIcon({\r\n browser: browserName,\r\n className: \"class\",\r\n style: { marginTop: 0 },\r\n size,\r\n })\r\n );\r\n\r\n return icon;\r\n}\r\n\r\n\r\nexport {PdfCheckBrowserIcon};","import React, { Component } from \"react\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport {\r\n getCurrentBrowser,\r\n getBrowserName,\r\n populateMessage,\r\n getSupportedVersionText,\r\n getOtherSupportedBrowsers,\r\n displayVersion,\r\n} from \"./pdf-browser-support\";\r\n\r\nimport { TextPanel } from \"../../../../exam/_presentation/text-panel\";\r\nimport { PdfCheckButtons } from \"./pdf-check-buttons\";\r\n\r\nimport List from \"@material-ui/core/List\";\r\nimport ListItem from \"@material-ui/core/ListItem\";\r\nimport ListItemText from \"@material-ui/core/ListItemText\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport { Align } from \"components/layout/align\";\r\nimport { PdfCheckBrowserIcon } from \"./pdf-check-browser-icon\";\r\n\r\nconst styles = ({ palette, spacing }) => ({\r\n support: {\r\n display: \"flex\",\r\n justifyContent: \"flex-start\",\r\n paddingTop: spacing.unit,\r\n paddingBottom: spacing.unit,\r\n \"&>div:first-child\": {\r\n paddingRight: spacing.unit,\r\n },\r\n },\r\n column: {\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n },\r\n});\r\n\r\nclass PdfCheckPanel extends Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.currentBrowser = getCurrentBrowser();\r\n }\r\n\r\n displayButtons(options) {\r\n const { proctoringMode, onContinue, messages } = this.props;\r\n const buttonProps = { proctoringMode, onContinue, messages, ...options };\r\n \r\n return ;\r\n }\r\n\r\n displayCurrentBrowser() {\r\n const { name, version, os } = this.currentBrowser;\r\n const text = (\r\n \r\n
Name : {name}
\r\n
Version : {version}
\r\n
OS : {os}
\r\n
\r\n
User Agent
\r\n
{navigator.userAgent}
\r\n
\r\n {this.displayButtons({canContinue: true})}\r\n
\r\n );\r\n return ;\r\n }\r\n\r\n displayBrowserSupport() {\r\n const { browserSupport } = this.props;\r\n const { versionNotSupported, notSupported } =\r\n browserSupport;\r\n\r\n if (check.assigned(versionNotSupported)) {\r\n return this.displayBrowserVersionNotSupported(\r\n versionNotSupported\r\n );\r\n } else if (check.assigned(notSupported)) {\r\n return this.displayBrowserNotSupported();\r\n }\r\n }\r\n\r\n displayBrowserVersionNotSupported(supportedBrowser) {\r\n const { classes, messages } = this.props;\r\n const { version } = this.currentBrowser;\r\n const displayName = getBrowserName(supportedBrowser);\r\n const supportedVersionText = getSupportedVersionText(supportedBrowser);\r\n const iconProps={browser: supportedBrowser}\r\n\r\n const text = (\r\n \r\n
\r\n
\r\n
\r\n {populateMessage(messages.supported, [\r\n [\"version\", displayVersion(version)],\r\n [\"browser\", displayName],\r\n [\"supported\", supportedVersionText],\r\n ])}\r\n
\r\n
\r\n {this.displayOtherSupportedBrowsers()}\r\n
\r\n {this.displayButtons()}\r\n
\r\n );\r\n\r\n return ;\r\n }\r\n\r\n displayBrowserNotSupported() {\r\n const { classes, messages } = this.props;\r\n const { version } = this.currentBrowser;\r\n const browser={...this.currentBrowser};\r\n const iconProps={browser};\r\n\r\n const displayName = getBrowserName(browser);\r\n\r\n const text = (\r\n \r\n
\r\n
\r\n
\r\n {populateMessage(messages.notSupported, [\r\n [\"version\", displayVersion(version)],\r\n [\"browser\", displayName],\r\n ])}\r\n
\r\n
\r\n {this.displayOtherSupportedBrowsers()}\r\n
\r\n {this.displayButtons()}\r\n
\r\n );\r\n\r\n return ;\r\n }\r\n\r\n displayOtherSupportedBrowsers() {\r\n const { os } = this.currentBrowser;\r\n const { supportedBrowsers, browserSupport, messages } = this.props;\r\n\r\n if (!check.nonEmptyString(messages.otherSupported)) {\r\n return null;\r\n }\r\n\r\n const { versionNotSupported } = browserSupport;\r\n\r\n const otherSupportedBrowsers = getOtherSupportedBrowsers(\r\n supportedBrowsers,\r\n check.nonEmptyObject(versionNotSupported)\r\n ? versionNotSupported\r\n : null,\r\n os\r\n );\r\n\r\n if (!check.nonEmptyArray(otherSupportedBrowsers)) {\r\n return null;\r\n }\r\n return (\r\n \r\n
{messages.otherSupported}
\r\n
\r\n {otherSupportedBrowsers.map((browser) => {\r\n const props = {\r\n primary: getBrowserName(browser),\r\n secondary: getSupportedVersionText(browser),\r\n };\r\n const iconProps={browser};\r\n return (\r\n \r\n \r\n \r\n \r\n );\r\n })}\r\n
\r\n
\r\n );\r\n }\r\n\r\n render() {\r\n const { browserSupport, classes } = this.props;\r\n if (browserSupport.supported) {\r\n return {this.displayCurrentBrowser()} ;\r\n }\r\n return (\r\n \r\n \r\n {/* {this.displayCurrentBrowser()} */}\r\n {this.displayBrowserSupport()}\r\n
\r\n \r\n );\r\n }\r\n}\r\n\r\nPdfCheckPanel = withStyles(styles)(PdfCheckPanel);\r\n\r\nexport { PdfCheckPanel };\r\n","import { check } from \"@xams-utils/check-types\";\r\n\r\nconst isResource = (resource, resourceType) => {\r\n\r\n if (!check.nonEmptyString(resource)) {\r\n return false;\r\n }\r\n\r\n const parts = resource.split(\".\");\r\n if (!check.nonEmptyArray(parts)) {\r\n return false;\r\n }\r\n const extension = parts[parts.length - 1];\r\n\r\n if (check.nonEmptyArray(resourceType)) {\r\n return resourceType.some((item) => isResourceType(extension, item));\r\n } else if (check.nonEmptyString(resourceType)) {\r\n return isResourceType(extension, resourceType);\r\n }\r\n\r\n return false;\r\n};\r\n\r\nconst isResourceType = (resource, resourceType) => {\r\n if (\r\n !check.nonEmptyString(resource) ||\r\n !check.nonEmptyString(resourceType)\r\n ) {\r\n return false;\r\n }\r\n\r\n return (\r\n resource.toLowerCase().substring(0, resourceType.length) ===\r\n resourceType.toLowerCase()\r\n );\r\n};\r\n\r\nexport {isResource}","export default class PDFJsBackend {\r\n constructor(){\r\n this.displayed=false;\r\n }\r\n\r\n init = (source, element) => {\r\n if (!this.displayed) {\r\n const iframe = document.createElement('iframe');\r\n //debugger;\r\n iframe.id = 'pdfjs-iframe';\r\n iframe.src = `${process.env.PUBLIC_URL}/pdf/pdfjs-3.2.146-legacy-dist/web/viewer_restricted_v1.html?file=${source}`;\r\n iframe.style='height: calc(100% - 12px); width: 100%;';\r\n // iframe.width = '100%';\r\n // iframe.height = '99%';\r\n \r\n element.appendChild(iframe);\r\n this.displayed=true;\r\n }\r\n };\r\n}\r\n","\r\nconst getCachedUrl = (cachedPdfsData, pdfUrl) => {\r\n\treturn cachedPdfsData.get(pdfUrl);\r\n}\r\n\r\n\r\nexport {getCachedUrl}","import React from \"react\";\r\nimport { connect } from \"react-redux\";\r\nimport { check } from \"@xams-utils/check-types\";\r\nimport PDFJSBackend from \"components/pages/exam/_layout/side/panels/pdf_resource/pdf_js_viewer/pdf-js-backend\";\r\n\r\nimport { getCacheData } from \"redux/reducers/selectors\";\r\nimport { getCachedPdfData } from \"redux/reducers/cache/selectors\";\r\nimport { getCachedUrl } from \"redux/reducers/cache/pdfs/selectors\";\r\n\r\nclass PreloadPdfs extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.viewerRef = React.createRef();\r\n this.backend = new PDFJSBackend();\r\n\r\n this.loadPdf = this.loadPdf.bind(this);\r\n }\r\n\r\n componentDidMount() {\r\n this.loadPdf(0);\r\n }\r\n\r\n componentWillUnmount() {\r\n clearTimeout(this.timer);\r\n }\r\n\r\n loadPdf(currentUrl) {\r\n const { resourceUrls, onComplete, cachedPdfData } = this.props;\r\n if (\r\n !check.nonEmptyArray(resourceUrls) ||\r\n currentUrl >= resourceUrls.length\r\n ) {\r\n onComplete();\r\n } else {\r\n const resourceUrl = resourceUrls[currentUrl];\r\n\r\n const cachedUrl = getCachedUrl(cachedPdfData, resourceUrl);\r\n const finalResourceUrl = cachedUrl || resourceUrl;\r\n console.log(currentUrl, finalResourceUrl);\r\n\r\n const element = this.viewerRef.current;\r\n\r\n this.backend.init(finalResourceUrl, element);\r\n this.backend.displayed = false;\r\n this.timer = setTimeout(() => this.loadPdf(currentUrl + 1), 1000);\r\n }\r\n }\r\n\r\n render() {\r\n const { maxPageWidth } = this.props;\r\n\r\n return (\r\n
\r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => ({\r\n cachedPdfData: getCachedPdfData(getCacheData(store)),\r\n});\r\n\r\nPreloadPdfs = connect(mapStoreToProps)(PreloadPdfs);\r\n\r\nexport { PreloadPdfs };\r\n","// npm\r\nimport React, { Component } from \"react\";\r\nimport { check } from \"@xams-utils/check-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { withMessages } from \"components/hocs/messages\";\r\n\r\n// react (layout)\r\nimport { Align } from \"components/layout/align\";\r\n\r\n// react (concrete)\r\nimport { PdfCheckPanel } from \"./pdf-check-panel\";\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\n\r\n// redux (selectors)\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getContentData } from \"redux/reducers/exam/selectors\";\r\nimport { getResourceUrls } from \"redux/reducers/exam/content/resource-urls-selector\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getProctoringMode } from \"redux/reducers/settings/client/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getPdfSupportedBrowsers } from \"redux/reducers/settings/app/selectors\";\r\n\r\nimport { isResource } from \"../resource-helper\";\r\nimport { checkPdfBrowserSupport } from \"./pdf-browser-support\";\r\nimport { PreloadPdfs } from \"./preload-pdf\";\r\nimport { LoadingSpinner } from \"components/presentation/loading-spinner\";\r\n\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\n// PdfCheckPage (not connected to store)\r\n// -----------------------------------------------------------------\r\n\r\nclass PdfCheckPage extends Component {\r\n state = {};\r\n constructor(props) {\r\n super(props);\r\n\r\n this.pdfSupported = this.pdfSupported.bind(this);\r\n\r\n this.state = { preload: false, checking: true };\r\n }\r\n\r\n componentDidMount() {\r\n const { hasPdf } = this.props;\r\n\r\n if (this.isBrowserSupported()) {\r\n if (hasPdf) {\r\n this.setState({ preload: true });\r\n } else {\r\n this.pdfSupported();\r\n }\r\n }\r\n\r\n this.setState({ checking: false });\r\n }\r\n\r\n isBrowserSupported() {\r\n const { hasPdf, browserSupport } = this.props;\r\n // return !hasPdf;\r\n return !hasPdf || check.assigned(browserSupport.supported);\r\n }\r\n\r\n pdfSupported() {\r\n const { onSuccess } = this.props;\r\n\r\n onSuccess();\r\n }\r\n\r\n render() {\r\n const { preload, checking } = this.state;\r\n\r\n if (preload) {\r\n const { resourceUrls } = this.props;\r\n const props = { resourceUrls, onComplete: this.pdfSupported };\r\n return (\r\n \r\n \r\n ;\r\n \r\n \r\n );\r\n } else if (checking || this.isBrowserSupported()) {\r\n return null;\r\n }\r\n\r\n const { messages, proctoringMode, supportedBrowsers, browserSupport } =\r\n this.props;\r\n\r\n const title = messages[MESSAGE_IDS.PDF_CHECK.TITLE];\r\n\r\n const panelProps = {\r\n messages: {\r\n supported: messages[MESSAGE_IDS.PDF_CHECK.SUPPORTED],\r\n notSupported: messages[MESSAGE_IDS.PDF_CHECK.NOT_SUPPORTED],\r\n otherSupported: messages[MESSAGE_IDS.PDF_CHECK.OTHER_SUPPORTED],\r\n continue: messages[MESSAGE_IDS.PDF_CHECK.CONTINUE],\r\n cancel: messages[MESSAGE_IDS.PDF_CHECK.CANCEL],\r\n },\r\n proctoringMode,\r\n supportedBrowsers,\r\n browserSupport,\r\n onContinue: this.pdfSupported,\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\n// PdfCheckPage (connected to store)\r\n// -----------------------------------------------------------------\r\n\r\nPdfCheckPage = withMessages(PdfCheckPage);\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const contentData = getContentData(examData);\r\n\r\n const resourceUrls = getResourceUrls(contentData);\r\n const hasPdf = resourceUrls.some((resourceUrl) =>\r\n isResource(resourceUrl, \"pdf\")\r\n );\r\n\r\n const returnObj = { hasPdf };\r\n if (hasPdf) {\r\n const settingsData = getSettingsData(store);\r\n const appSettingsData = getAppSettingsData(settingsData);\r\n\r\n const supportedBrowsers = getPdfSupportedBrowsers(appSettingsData);\r\n\r\n returnObj.supportedBrowsers = supportedBrowsers;\r\n returnObj.browserSupport = checkPdfBrowserSupport(supportedBrowsers);\r\n returnObj.proctoringMode = getProctoringMode(\r\n getClientSettingsData(settingsData)\r\n );\r\n\r\n if (returnObj.browserSupport.supported) {\r\n returnObj.resourceUrls = resourceUrls.filter((resourceUrl) =>\r\n isResource(resourceUrl, \"pdf\")\r\n );\r\n }\r\n } else {\r\n returnObj.browserSupport = { supported: true };\r\n }\r\n\r\n return returnObj;\r\n};\r\n\r\nPdfCheckPage = connect(mapStoreToProps)(PdfCheckPage);\r\n\r\n// PdfCheckPage (EXPORT)\r\n// -----------------------------------------------------------------\r\nexport { PdfCheckPage };\r\n","const PROCTORING_TYPES = {\r\n INTEGRITY_ADVOCATE: \"Integrity Advocate\",\r\n PROCTORIO: \"Proctorio\"\r\n};\r\n\r\nexport { PROCTORING_TYPES };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { getGuid } from \"redux/reducers/exam/selectors\";\r\nimport { getExamData, getSchedulesData } from \"redux/reducers/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getScheduleDataByExamGuid } from \"redux/reducers/schedules/selectors\";\r\nimport {\r\n getProctoringMode,\r\n getProctoringType,\r\n getIntegrityAdvocateAppId,\r\n} from \"redux/reducers/settings/client/selectors\";\r\nimport { getSessionData } from \"redux/reducers/selectors\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport {\r\n getUserId,\r\n getFirstName,\r\n getLastName,\r\n getEmail,\r\n} from \"redux/reducers/session/user/selectors\";\r\nimport {\r\n getProctorProvider,\r\n getObjectId,\r\n getQualificationId,\r\n} from \"redux/reducers/schedules/schedule/selectors\";\r\n\r\nimport { PROCTORING_TYPES } from \"constants/proctoring\";\r\n\r\nimport { Align } from \"components/layout/align\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\nimport { LoadingSpinner } from \"components/presentation/loading-spinner\";\r\n\r\nclass OpenIntegrityAdvocate extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleIntegrityAdvocateReady =\r\n this.handleIntegrityAdvocateReady.bind(this);\r\n this.handleScriptLoaded = this.handleScriptLoaded.bind(this);\r\n\r\n this.state = { error: null, loaded: false };\r\n }\r\n\r\n componentDidMount() {\r\n if (this.isIntegrityAdvocate()) {\r\n this.addIntegrityAdvocateScript();\r\n\r\n document.addEventListener(\r\n \"IA_Ready\",\r\n this.handleIntegrityAdvocateReady\r\n );\r\n } else {\r\n const { onSuccess } = this.props;\r\n console.log(\"Not Integrity Advocate\");\r\n onSuccess();\r\n }\r\n }\r\n\r\n componentWillUnmount() {\r\n if (this.isIntegrityAdvocate()) {\r\n document.removeEventListener(\r\n \"IA_Ready\",\r\n this.handleIntegrityAdvocateReady\r\n );\r\n\r\n if (this.script) {\r\n this.script.removeEventListener(\r\n \"load\",\r\n this.handleScriptLoaded\r\n );\r\n this.script.removeEventListener(\r\n \"error\",\r\n this.handleScriptLoaded\r\n );\r\n }\r\n }\r\n }\r\n\r\n isIntegrityAdvocate() {\r\n const { proctoringProvider, integrityAdvocateAppId } = this.props;\r\n\r\n return (\r\n proctoringProvider === PROCTORING_TYPES.INTEGRITY_ADVOCATE &&\r\n check.nonEmptyString(integrityAdvocateAppId)\r\n );\r\n }\r\n\r\n addIntegrityAdvocateScript() {\r\n const error = this.checkParmeters();\r\n if (check.nonEmptyObject(error)) {\r\n this.setState({ error });\r\n return;\r\n }\r\n\r\n const {\r\n firstName,\r\n lastName,\r\n userId,\r\n email,\r\n objectId,\r\n qualificationId,\r\n integrityAdvocateAppId,\r\n } = this.props;\r\n // const appId = 'ff4b26d6-7bba-4c62-adf2-84d1109d354f';\r\n\r\n let url =\r\n \"https://ca.integrityadvocateserver.com/participants/integrity\";\r\n url += `?appid=${integrityAdvocateAppId}`;\r\n url += `&participantidentifier=${userId}`;\r\n url += `&courseid=${qualificationId}`;\r\n url += `&participantfirstname=${firstName}`;\r\n url += `&participantlastname=${lastName}`;\r\n url += `&participantemail=${email}`;\r\n url += `&activityid=${objectId}`;\r\n\r\n this.script = document.createElement(\"script\");\r\n this.script.type = \"application/javascript\";\r\n this.script.src = url;\r\n this.script.async = true;\r\n\r\n document.body.appendChild(this.script);\r\n\r\n this.script.addEventListener(\"load\", this.handleScriptLoaded);\r\n this.script.addEventListener(\"error\", this.handleScriptLoaded);\r\n }\r\n\r\n checkParmeters() {\r\n const {\r\n firstName,\r\n lastName,\r\n userId,\r\n email,\r\n objectId,\r\n qualificationId,\r\n } = this.props;\r\n\r\n if (!check.nonEmptyString(firstName)) {\r\n return { text: \"First name is blank\" };\r\n } else if (!check.nonEmptyString(lastName)) {\r\n return { text: \"Last name is blank\" };\r\n } else if (!check.integer(userId)) {\r\n return { text: \"User ID is blank\" };\r\n } else if (!check.nonEmptyString(email)) {\r\n return { text: \"Email is blank\" };\r\n } else if (\r\n !check.integer(qualificationId) &&\r\n !check.nonEmptyString(qualificationId)\r\n ) {\r\n return { text: \"qualification ID (course ID) is blank\" };\r\n } else if (\r\n !check.integer(objectId) &&\r\n !check.nonEmptyString(objectId)\r\n ) {\r\n return { text: \"activity ID (object ID) is blank\" };\r\n }\r\n\r\n return null;\r\n }\r\n\r\n handleIntegrityAdvocateReady(e) {\r\n const { onSuccess } = this.props;\r\n console.log(\"handleIntegrityAdvocateReady\", e);\r\n onSuccess();\r\n }\r\n\r\n handleScriptLoaded(e) {\r\n console.log(\"handleScriptLoaded\", e);\r\n if (e.type === \"load\") {\r\n console.log(\"Integrity Advocate Script Loaded\", e);\r\n this.setState({ loaded: true });\r\n } else {\r\n console.log(\"Integrity Advocate Script Error\", e);\r\n this.setState({ error: e });\r\n }\r\n }\r\n\r\n displayError(_error) {\r\n const error = check.nonEmptyObject(_error)\r\n ? _error\r\n : { text: \"Error loading script\" };\r\n\r\n const a = [];\r\n for (const [key, value] of Object.entries(error)) {\r\n a.push([key, value]);\r\n }\r\n\r\n return (\r\n \r\n {a.map((item) => (\r\n
\r\n \r\n {item[0]} : {item[1]}\r\n \r\n
\r\n ))}\r\n
\r\n );\r\n }\r\n\r\n render() {\r\n if (!this.isIntegrityAdvocate()) {\r\n return null;\r\n }\r\n\r\n const { error } = this.state;\r\n const title = \"Integrity Advocate\";\r\n\r\n if (error) {\r\n return (\r\n \r\n \r\n \r\n \r\n
\r\n \r\n {title} Error\r\n \r\n
\r\n
{this.displayError(error)}
\r\n
\r\n \r\n \r\n );\r\n }\r\n\r\n return (\r\n \r\n \r\n ;\r\n \r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const examGuid = getGuid(examData);\r\n\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n const schedulesData = getSchedulesData(store);\r\n const scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n const proctoringType = getProctoringType(clientSettingsData);\r\n const proctoringMode = getProctoringMode(clientSettingsData);\r\n const proctoringProvider = getProctorProvider(scheduleData);\r\n const objectId = getObjectId(scheduleData);\r\n const qualificationId = getQualificationId(scheduleData);\r\n\r\n const userSessionData = getUserSessionData(getSessionData(store));\r\n const firstName = getFirstName(userSessionData);\r\n const lastName = getLastName(userSessionData);\r\n const userId = getUserId(userSessionData);\r\n const email = getEmail(userSessionData);\r\n\r\n const integrityAdvocateAppId =\r\n getIntegrityAdvocateAppId(clientSettingsData);\r\n\r\n return {\r\n proctoringMode,\r\n proctoringType,\r\n proctoringProvider,\r\n firstName,\r\n lastName,\r\n userId,\r\n email,\r\n examGuid,\r\n objectId,\r\n qualificationId,\r\n integrityAdvocateAppId,\r\n };\r\n};\r\n\r\nOpenIntegrityAdvocate = connect(mapStoreToProps)(OpenIntegrityAdvocate);\r\n\r\nexport { OpenIntegrityAdvocate };\r\n","import React, { Component } from \"react\";\r\nimport { withRouter } from \"react-router-dom\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getGuid } from \"redux/reducers/exam/selectors\";\r\nimport { getExamData, getSchedulesData } from \"redux/reducers/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getScheduleDataByExamGuid } from \"redux/reducers/schedules/selectors\";\r\nimport {\r\n getProctoringMode,\r\n getProctoringType,\r\n} from \"redux/reducers/settings/client/selectors\";\r\n\r\nimport {\r\n getProctorProvider,\r\n getProctoringSessionID,\r\n} from \"redux/reducers/schedules/schedule/selectors\";\r\n\r\nimport { getProctorioData } from \"redux/reducers/selectors\";\r\nimport { getParent, getDebugMode } from \"redux/reducers/proctorio/selectors\";\r\n\r\nimport { PROCTORING_TYPES } from \"constants/proctoring\";\r\n\r\nclass CheckingProctorio extends Component {\r\n state = {};\r\n\r\n componentDidMount() {\r\n if (this.isProctorio()) {\r\n const { proctorProvider, proctoringSessionId, examGuid } =\r\n this.props;\r\n const url = `/proctoring/${proctorProvider}/${proctoringSessionId}/${examGuid}`;\r\n\r\n this.props.history.push(url);\r\n } else {\r\n const { onSuccess } = this.props;\r\n console.log(\"Not Proctorio\");\r\n\r\n onSuccess();\r\n }\r\n }\r\n\r\n isProctorio() {\r\n const { proctorProvider, proctorioActive } = this.props;\r\n\r\n return proctorProvider === PROCTORING_TYPES.PROCTORIO && !proctorioActive;\r\n }\r\n\r\n render() {\r\n return null;\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const examGuid = getGuid(examData);\r\n\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n const schedulesData = getSchedulesData(store);\r\n const scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n const proctoringType = getProctoringType(clientSettingsData);\r\n const proctoringMode = getProctoringMode(clientSettingsData);\r\n\r\n const proctoringSessionId = getProctoringSessionID(scheduleData);\r\n const proctorProvider = getProctorProvider(scheduleData);\r\n\r\n const proctorioData = getProctorioData(store);\r\n const proctorioActive = getParent(proctorioData);\r\n\r\n return {\r\n proctoringMode,\r\n proctoringType,\r\n proctorProvider,\r\n proctoringSessionId,\r\n examGuid,\r\n proctorioActive,\r\n };\r\n};\r\n\r\nCheckingProctorio = connect(mapStoreToProps)(CheckingProctorio);\r\n\r\nCheckingProctorio = withRouter(CheckingProctorio);\r\n\r\nexport { CheckingProctorio };\r\n","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\n\r\nimport { PdfCheckPage } from './pdf_check/pdf-check-page';\r\nimport { OpenIntegrityAdvocate } from './proctoring/open-integrity-advocate';\r\nimport { CheckingProctorio } from './proctoring/checking-proctorio';\r\n\r\n\r\n// machines\r\nimport {examInit} from 'machines/exam-init'\r\n\r\n\r\nconst {EVENTS, CHECKING_RESOURCES_STATES:STATES} = examInit;\r\n\r\n\r\nconst CheckingResourcesView = () =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t{{\r\n\t\t\t\t[STATES.CHECKING_PDF]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\r\n\t\t\t\t[STATES.CHECKING_INTEGRITY_ADVOCATE]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\t\t\t\t\r\n\t\t\t\t[STATES.CHECKING_PROCTORIO]: () => (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t \r\n\t\t\t\t),\t\t\t\t\t\t\t\t\r\n\t\t\t}}\r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {CheckingResourcesView}\r\n\r\n","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {ExamPasswordPage} from './password/page'\r\nimport {InitializingExamView} from './initializing_exam/view'\r\nimport {ValidatingScheduleView} from './validating_schedule/view'\r\nimport {CheckingResourcesView} from './checking_resources/view'\r\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\r\n\r\n// machines\r\nimport {examInit} from 'machines/exam-init'\r\n\r\n\r\nconst {EVENTS, INITIALIZING_STATES:STATES} = examInit;\r\n\r\n\r\nconst InitializingView = (examGuid) =>\r\n{\r\n\tconst appBarProps = {\r\n\t\ttitle: \"Initializing your exam\",\r\n\t\tloadingTitle: true,\r\n\t\tlogo: true,\r\n\t\tlogout: false\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t\t\r\n\t\t\t\t{{\r\n\t\t\t\t\t[STATES.VALIDATING_SCHEDULE]: () => (\r\n\t\t\t\t\t\tValidatingScheduleView(examGuid)\r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.INPUTTING_PASSWORD]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{({onFinish}) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.INITIALIZING_EXAM]: () => (\r\n\t\t\t\t\t\tInitializingExamView()\r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.CHECKING_RESOURCES]: () => (\r\n\t\t\t\t\t\tCheckingResourcesView()\r\n\t\t\t\t\t)\t\r\n\t\t\t\t}}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {InitializingView}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Drawer from '@material-ui/core/Drawer'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// constants\r\nimport {contentBarHeight} from '../constants'\r\nimport {appBarHeight} from 'components/pages/constants'\r\n\r\n\r\n// SideDrawer (not connected to styles)\r\n// ------------------------------------------------\r\n\r\nlet SideDrawer = ({classes, anchor, open, children}) =>\r\n{\r\n\tconst drawerProps = {classes, anchor, open};\r\n\tconst offset = appBarHeight + contentBarHeight;\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{children}\r\n\t\t\t
\r\n\t\t \r\n\t)\r\n}\r\n\r\nSideDrawer.propTypes = {\r\n\tclasses: PropTypes.object.isRequired,\r\n\tanchor: PropTypes.string.isRequired,\r\n\topen: PropTypes.bool.isRequired,\r\n\tchildren: PropTypes.node.isRequired\r\n}\r\n\r\n\r\n// SideDrawer (connected to styles)\r\n// ------------------------------------------------\r\n\r\nconst styles = ({zIndex, palette}) => ({\r\n\tpaper: {\r\n\t\tzIndex: zIndex.appBar - 2,\r\n\t\tbackground: palette.background.light + \"!important\"\r\n\t},\r\n\tmodal: {\r\n\t\tzIndex: zIndex.appBar - 1\r\n\t}\r\n});\r\n\r\nSideDrawer = withStyles(styles)(SideDrawer);\r\n\r\n\r\n// Export\r\n// ------------------------------------------------\r\nexport {SideDrawer}","\r\n// npm\r\nimport React from 'react'\r\n\r\n\r\n// Size = Stretch to fill as much space possible\r\n// Flex-align children along the vertical axis\r\nconst style = {\r\n\tdisplay: 'flex',\r\n\tflexDirection: 'column',\r\n\talignItems: 'stretch',\r\n\tflexGrow: 1\r\n}\r\n\r\nconst ContentBox = ({children}) => (\r\n\t{children}
\r\n)\r\n\r\n\r\nexport {ContentBox}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport IconButton from '@material-ui/core/IconButton'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n\r\nlet DrawerIconButton = ({Icon, open, onClick, classes}) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t \r\n\t);\r\n}\r\n\r\nDrawerIconButton.propTypes = {\r\n\tIcon: PropTypes.oneOfType([\r\n\t\tPropTypes.func,\r\n\t\tPropTypes.object\r\n\t]).isRequired,\r\n\topen: PropTypes.bool,\r\n\tonClick: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nconst styles = ({palette}) => ({\r\n\tclosed: {color: palette.background.contrastText},\r\n\topen: {color: palette.primary.main}\r\n});\r\n\r\nDrawerIconButton = withStyles(styles)(DrawerIconButton);\r\n\r\n\r\nexport {DrawerIconButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport AppBar from '@material-ui/core/AppBar'\r\nimport Toolbar from '@material-ui/core/Toolbar'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {DrawerIconButton} from './bar/drawer-icon-button'\r\n\r\n// constants\r\nimport {contentBarHeight} from '../constants'\r\n\r\n\r\n// ContentBar (not connected to styles)\r\n// ---------------------------------------------------\r\n\r\nlet ContentBar = (props) =>\r\n{\r\n\tconst {left, right} = props;\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{left && }\r\n\t\t\t\t\r\n\t\t\t\t\t{props.children}\r\n\t\t\t\t
\r\n\t\t\t\t{right && }\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\nContentBar.propTypes = {\r\n\tleft: PropTypes.shape({\r\n\t\tIcon: PropTypes.oneOfType([\r\n\t\t\tPropTypes.func,\r\n\t\t\tPropTypes.instanceOf(React.Component)\r\n\t\t]).isRequired,\r\n\t\topen: PropTypes.bool.isRequired,\r\n\t\tonClick: PropTypes.func.isRequired\r\n\t}),\r\n\tright: PropTypes.shape({\r\n\t\tIcon: PropTypes.oneOfType([\r\n\t\t\tPropTypes.func,\r\n\t\t\tPropTypes.instanceOf(React.Component)\r\n\t\t]).isRequired,\r\n\t\topen: PropTypes.bool.isRequired,\r\n\t\tonClick: PropTypes.func.isRequired\r\n\t})\r\n}\r\n\r\n\r\n// ContentBar (connected to styles)\r\n// ---------------------------------------------------\r\n\r\nconst styles = ({palette}) =>\r\n{\r\n\treturn {\r\n\t\troot: {\r\n\t\t\tposition: 'relative',\r\n\t\t\theight: contentBarHeight,\r\n\t\t\tbackgroundColor: palette.background.light\r\n\t\t}\r\n\t}\r\n}\r\n\r\nContentBar = withStyles(styles)(ContentBar);\r\n\r\n\r\n// ContentBar (not connected to styles)\r\n// ---------------------------------------------------\r\nexport {ContentBar}","const contentBarHeight = 64;\r\nconst heightOffset = 64;\r\n\r\nexport {\r\n\tcontentBarHeight,\r\n\theightOffset\r\n}","\r\n// npm\r\nimport React from 'react'\r\nimport check from 'check-types'\r\nimport ReactDOM from 'react-dom'\r\nimport PropTypes from 'prop-types'\r\n\r\n\r\nclass GetDomElement extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.childRef = React.createRef();\r\n\t\tthis.domElement = null;\r\n\t}\r\n\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tthis.updateDomElement();\r\n\t}\r\n\r\n\tcomponentDidUpdate(previousProps)\r\n\t{\r\n\t\tif (previousProps.children !== this.props.children)\r\n\t\t{\r\n\t\t\tthis.updateDomElement();\r\n\t\t}\r\n\t}\r\n\r\n\tupdateDomElement()\r\n\t{\r\n\t\tconst currentDomElement = this.findDomElement();\r\n\t\tthis.setDomElement(currentDomElement);\r\n\t}\r\n\r\n\tfindDomElement()\r\n\t{\r\n\t\tlet element = this.childRef.current;\r\n\t\tconst isElement = check.instanceStrict(element, Element);\r\n\t\tif (isElement) { return element; }\r\n\r\n\t\telement = ReactDOM.findDOMNode(this.childRef.current);\r\n\t\tif (element !== null) { return element; }\r\n\t\t\r\n\t\tthrow ```\r\n\t\t\tChild must be one of the following:\r\n\t\t\t- a DOM react instance\r\n\t\t\t- a React element declared as a class\r\n\t\t```;\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn React.cloneElement(this.props.children, {ref: this.childRef});\r\n\t}\r\n\r\n\tsetDomElement(domElement)\r\n\t{\r\n\t\tthis.domElement = domElement;\r\n\t\tthis.props.onDomElement(domElement);\r\n\t}\r\n}\r\n\r\nGetDomElement.propTypes = {\r\n\tchildren: PropTypes.element.isRequired,\r\n\tonDomElement: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nexport {GetDomElement}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {GetDomElement} from 'components/functional/get-dom-element'\r\n\r\n// styles\r\nimport './material-scroll-wrapper.css'\r\n\r\n\r\nclass MaterialScrollWrapper extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.updateScrollStyling = (domElement) => {\r\n\t\t\tconst {horizontal, vertical} = this.props;\r\n\t\t\tdomElement.classList.add('material-scroll');\r\n\t\t\tif (horizontal) { domElement.classList.add('horizontal-scroll'); }\r\n\t\t\tif (vertical) { domElement.classList.add('vertical-scroll'); }\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.props.children}\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n}\r\n\r\nMaterialScrollWrapper.propTypes = {\r\n\tchildren: PropTypes.element.isRequired,\r\n\tvertical: PropTypes.bool.isRequired,\r\n\thorizontal: PropTypes.bool.isRequired\r\n}\r\n\r\nMaterialScrollWrapper.defaultProps = {\r\n\tvertical: true,\r\n\thorizontal: true\r\n}\r\n\r\n\r\nexport {MaterialScrollWrapper}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// react\r\nimport {MaterialScrollWrapper} from 'components/layout/scroll_wrapper/material-scroll-wrapper'\r\n\r\n\r\nconst ContentPanel = ({children}) => (\r\n\t\r\n\t\t\r\n\t\t\t{children}\r\n\t\t
\r\n\t \r\n)\r\n\r\n\r\nexport {ContentPanel}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {SideDrawer} from './side/drawer'\r\nimport {ContentBox} from './content/content-box'\r\nimport {ContentBar} from './content/content-bar'\r\nimport {ContentPanel} from './content/content-panel'\r\n\r\n\r\nclass MobileExamLayout extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.state = {leftDrawerOpen: false, rightDrawerOpen: false};\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.toggleDrawer = (side) => () => {\r\n\t\t\tconst stateProperty = `${side}DrawerOpen`;\r\n\t\t\tthis.setState({[stateProperty]: !this.state[stateProperty]});\r\n\t\t}\r\n\r\n\t\tthis.closeDrawers = () => {\r\n\t\t\tthis.setState({leftDrawerOpen: false, rightDrawerOpen: false});\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.getDrawer('left')}\r\n\t\t\t\t\r\n\t\t\t\t\t{this.ContentBar}\r\n\t\t\t\t\t{this.ContentPanel}\r\n\t\t\t\t \r\n\t\t\t\t{this.getDrawer('right')}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget ContentBar()\r\n\t{\r\n\t\tconst contentBarProps = {};\r\n\r\n\t\tif (this.props.left) {\r\n\t\t\tcontentBarProps.left = {\r\n\t\t\t\tIcon: this.props.left.Icon,\r\n\t\t\t\topen: this.state.leftDrawerOpen,\r\n\t\t\t\tonClick: this.toggleDrawer('left')\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (this.props.right) {\r\n\t\t\tcontentBarProps.right = {\r\n\t\t\t\tIcon: this.props.right.Icon,\r\n\t\t\t\topen: this.state.rightDrawerOpen,\r\n\t\t\t\tonClick: this.toggleDrawer('right')\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.props.bar.content}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tgetDrawer(side)\r\n\t{\r\n\t\tconst drawerConfig = this.props[side];\r\n\t\tif (!drawerConfig) { return null; }\r\n\r\n\t\tconst drawerProps = {\r\n\t\t\tanchor: side,\r\n\t\t\topen: this.state[`${side}DrawerOpen`]\r\n\t\t}\r\n\r\n\t\treturn {drawerConfig.content} ;\r\n\t}\r\n\r\n\tget ContentPanel()\r\n\t{\r\n\t\treturn {this.props.centre.content} ;\r\n\t}\r\n}\r\n\r\nMobileExamLayout.propTypes = {\r\n\tbar: PropTypes.shape({\r\n\t\tcontent: PropTypes.node\r\n\t}),\r\n\tleft: PropTypes.shape({\r\n\t\tcontent: PropTypes.node,\r\n\t\tprioritize: PropTypes.bool,\r\n\t\tIcon: PropTypes.instanceOf(React.Component)\r\n\t}),\r\n\tcentre: PropTypes.shape({\r\n\t\tcontent: PropTypes.node\r\n\t}),\r\n\tright: PropTypes.shape({\r\n\t\tcontent: PropTypes.node,\r\n\t\tprioritize: PropTypes.bool,\r\n\t\tIcon: PropTypes.instanceOf(React.Component)\r\n\t})\r\n}\r\n\r\n\r\nexport {MobileExamLayout}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Paper from '@material-ui/core/Paper'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n\r\n// SidePanel (not connected to styles)\r\n// --------------------------------------------\r\n\r\nlet SidePanel = ({classes, children}) => (\r\n\t\r\n\t\t
\r\n\t\t\t{children}\r\n\t\t \r\n\t
\r\n)\r\n\r\nSidePanel.propTypes = {\r\n\tchildren: PropTypes.node.isRequired,\r\n\tclasses: PropTypes.object.isRequired\r\n}\r\n\r\n\r\n// SidePanel (connected to styles)\r\n// --------------------------------------------\r\n\r\nconst styles = ({zIndex, palette}) => ({\r\n\tpaper: {\r\n\t\theight: '100%',\r\n\t\tbackgroundColor: palette.background.light\r\n\t},\r\n\tpanel: {\r\n\t\tflexShrink: 0,\r\n\t\tzIndex: zIndex.appBar\r\n\t}\r\n})\r\n\r\nSidePanel = withStyles(styles)(SidePanel);\r\n\r\n\r\n// Export\r\n// --------------------------------------------\r\nexport {SidePanel}","\r\n// Responsibility:\r\n// To notify when the bounds of the incoming child's DOM element has changed\r\n\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {withWindowSizeListener} from 'react-window-size-listener'\r\n\r\n// react\r\nimport {GetDomElement} from './get-dom-element'\r\n\r\n\r\nconst MUTATION_OBSERVER_CONFIG = {\r\n\tattributes: true,\r\n\tattributeFilter: ['style']\r\n}\r\n\r\n\r\nclass BoundsListener extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.domElement = null;\r\n\t\tthis.elementPos = {x: null, y: null};\r\n\t\tthis.elementSize = {w: null, h: null};\r\n\t\tthis.initializeBoundMethods();\r\n\t\tthis.observer = new MutationObserver(this.notifyChangeInElementBounds);\r\n\t}\r\n\r\n\tcomponentDidUpdate(prevProps)\r\n\t{\r\n\t\tif (prevProps.windowSize !== this.props.windowSize)\r\n\t\t{\r\n\t\t\tthis.notifyChangeInElementBounds();\r\n\t\t}\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.notifyChangeInElementBounds = () => {\r\n\t\t\tconst newElementRect = this.domElement.getBoundingClientRect();\r\n\t\t\tconst {onPositionChange, onSizeChange} = this.props;\r\n\r\n\t\t\tif (onPositionChange) {\r\n\t\t\t\tconst newPosition = this.getNewPosition(newElementRect);\r\n\t\t\t\tif (newPosition) {\r\n\t\t\t\t\tthis.elementPos.x = newPosition.x;\r\n\t\t\t\t\tthis.elementPos.y = newPosition.y;\r\n\t\t\t\t\tonPositionChange(newPosition.x, newPosition.y);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (onSizeChange) {\r\n\t\t\t\tconst newSize = this.getNewSize(newElementRect);\r\n\t\t\t\tif (newSize) {\r\n\t\t\t\t\tthis.elementSize.w = newSize.w;\r\n\t\t\t\t\tthis.elementSize.h = newSize.h;\r\n\t\t\t\t\tonSizeChange(newSize.w, newSize.h);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.updateObserver = () => {\r\n\t\t\tthis.observer.disconnect();\r\n\t\t\tthis.observer.observe(this.domElement, MUTATION_OBSERVER_CONFIG);\r\n\t\t}\r\n\r\n\t\tthis.updateDomElement = (domElement) => {\r\n\t\t\tthis.domElement = domElement;\r\n\t\t\tthis.updateObserver();\r\n\t\t\tthis.notifyChangeInElementBounds();\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.props.children}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tgetNewPosition({left:x, top:y})\r\n\t{\r\n\t\tconst relativeToDocument = this.props.relativeTo === 'document';\r\n\t\tx += relativeToDocument ? window.pageXOffset : 0;\r\n\t\ty += relativeToDocument ? window.pageYOffset : 0;\r\n\t\tif (this.elementPos.x !== x || this.elementPos.y !== y) { return {x, y}; }\r\n\t}\r\n\r\n\tgetNewSize({top, left, bottom, right})\r\n\t{\r\n\t\tconst w = right - left;\r\n\t\tconst h = bottom - top;\r\n\t\tif (this.elementSize.w !== w || this.elementSize.h !== h) { return {w, h}; }\r\n\t}\r\n}\r\n\r\nBoundsListener.propTypes = {\r\n\trelativeTo: PropTypes.oneOf(['viewport', 'document']).isRequired,\t\t\t\t\t\t\t\t// use 'document' for position changes that shouldn't be affected by window scroll\r\n\tchildren: PropTypes.element.isRequired,\r\n\tonPositionChange: PropTypes.func,\r\n\tonSizeChange: PropTypes.func\r\n}\r\n\r\nBoundsListener.defaultProps = {\r\n\trelativeTo: 'viewport'\r\n}\r\n\r\n\r\nBoundsListener = withWindowSizeListener(BoundsListener);\r\n\r\n\r\nexport {BoundsListener}","\r\n// npm\r\nimport React from 'react'\r\nimport check from 'check-types'\r\nimport PropTypes from 'prop-types'\r\nimport {withWindowSizeListener} from 'react-window-size-listener'\r\n\r\n// react\r\nimport {SidePanel} from './side/panel'\r\nimport {SideDrawer} from './side/drawer'\r\nimport {ContentBox} from './content/content-box'\r\nimport {ContentBar} from './content/content-bar'\r\nimport {ContentPanel} from './content/content-panel'\r\nimport {BoundsListener} from 'components/functional/bounds-listener'\r\n\r\n\r\nconst MIN_CONTENT_WIDTH = 600;\r\n\r\n\r\n// DesktopExamLayout (not connected to windowSizeListener)\r\n// -------------------------------------------------------------\r\n\r\nclass DesktopExamLayout extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {\r\n\t\t\tleftDrawerOpen: null,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// null means no drawer (use panel instead)\r\n\t\t\trightDrawerOpen: null\r\n\t\t}\r\n\r\n\t\tthis.leftContentWidth = 0;\r\n\t\tthis.rightContentWidth = 0;\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.setPanel = (side) => () => {\r\n\t\t\tthis.setState({[`${side}DrawerOpen`]: null});\r\n\t\t}\r\n\r\n\t\tthis.setDrawer = (side) => () => {\r\n\t\t\tthis.setState({[`${side}DrawerOpen`]: false});\r\n\t\t}\r\n\r\n\t\tthis.toggleDrawer = (side) => () => {\r\n\t\t\tconst stateProperty = `${side}DrawerOpen`;\r\n\t\t\tif (stateProperty === null) { throw \"Drawer not available\"; }\r\n\t\t\tthis.setState({[stateProperty]: !this.state[stateProperty]});\r\n\t\t}\r\n\r\n\t\tthis.closeDrawers = () => {\r\n\t\t\tthis.setState({leftDrawerOpen: false, rightDrawerOpen: false});\r\n\t\t}\r\n\r\n\t\tthis.isDrawer = (side) => {\r\n\t\t\treturn this.state[`${side}DrawerOpen`] !== null;\r\n\t\t}\r\n\r\n\t\tthis.findAvailableSidePanels = () => {\r\n\t\t\tconst availableSidePanels = [];\r\n\t\t\tif (this.props.left) { availableSidePanels.push('left'); }\t\t\t\t\t\t\t\t\t// getPanelSidePriority depends on this push order!\r\n\t\t\tif (this.props.right) { availableSidePanels.push('right'); }\r\n\t\t\treturn availableSidePanels;\r\n\t\t}\r\n\r\n\t\tthis.getPanelSidePriority = () => { // left defaults as priority\r\n\t\t\tconst availableSidePanels = this.findAvailableSidePanels();\r\n\t\t\tif (availableSidePanels.length < 2) { return availableSidePanels; }\r\n\t\t\tif (this.props.left.prioritize) { return availableSidePanels; }\r\n\t\t\tif (!this.props.right.prioritize) { return availableSidePanels; }\r\n\t\t\treturn ['right', 'left'];\r\n\t\t}\r\n\r\n\t\tthis.findPanelSidesThatFitOnScreen = (panelSidesByPriority) => {\r\n\t\t\tconst result = [];\r\n\r\n\t\t\tconst viewportWidth = document.body.clientWidth;\r\n\t\t\tlet totalContentWidth = MIN_CONTENT_WIDTH;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// this value increases with every side panel we check throughout function\r\n\t\t\tif (viewportWidth < totalContentWidth) { return result; }\r\n\r\n\t\t\tfor (let i = 0; i < panelSidesByPriority.length; i++) {\r\n\t\t\t\tconst nextPanelSide = panelSidesByPriority[i];\r\n\t\t\t\ttotalContentWidth += this[`${nextPanelSide}ContentWidth`];\r\n\t\t\t\tif (viewportWidth < totalContentWidth) { break; }\r\n\t\t\t\tresult.push(nextPanelSide);\r\n\t\t\t}\r\n\r\n\t\t\treturn result;\r\n\t\t}\r\n\r\n\t\tthis.updatePanelStates = () => {\r\n\t\t\tconst panelSidesByPriority = this.getPanelSidePriority();\r\n\t\t\tconst panelSidesThatFit = this.findPanelSidesThatFitOnScreen(panelSidesByPriority);\r\n\t\t\tconst newState = {};\r\n\t\t\tpanelSidesByPriority.forEach(panelSide => {\r\n\t\t\t\tconst statePropertyName = `${panelSide}DrawerOpen`;\r\n\t\t\t\tconst panelFits = panelSidesThatFit.includes(panelSide);\r\n\t\t\t\tconst isDrawer = this.isDrawer(panelSide);\r\n\t\t\t\tif (panelFits && isDrawer) { newState[statePropertyName] = null; }\r\n\t\t\t\telse if (!panelFits && !isDrawer) { newState[statePropertyName] = false; }\r\n\t\t\t})\r\n\t\t\tif (!check.emptyObject(newState)) { this.setState(newState); }\r\n\t\t}\r\n\r\n\t\tthis.updatePanelWidth = (side) => (width) => {\r\n\t\t\tthis[`${side}ContentWidth`] = width;\r\n\t\t\tthis.updatePanelStates();\r\n\t\t}\r\n\t}\r\n\r\n\tcomponentDidUpdate(prevProps)\r\n\t{\r\n\t\tconst oldWindowSize = prevProps.windowSize;\r\n\t\tconst newWindowSize = this.props.windowSize;\r\n\r\n\t\tif (oldWindowSize !== newWindowSize)\r\n\t\t{\r\n\t\t\tconst existsDrawer = this.isDrawer('left') || this.isDrawer('right');\r\n\t\t\tconst widthIncreased = newWindowSize.windowWidth > oldWindowSize.windowWidth;\r\n\t\t\tif (existsDrawer && widthIncreased) { this.updatePanelStates(); }\r\n\r\n\t\t\tconst existsPanel = !this.isDrawer('left') || !this.isDrawer('right');\r\n\t\t\tif (existsPanel && !widthIncreased) { this.updatePanelStates(); }\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.getPanelOrDrawer('left')}\r\n\t\t\t\t{this.Content}\r\n\t\t\t\t{this.getPanelOrDrawer('right')}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget Content()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.ContentBar}\r\n\t\t\t\t{this.ContentPanel}\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tget ContentBar()\r\n\t{\r\n\t\tconst contentBarProps = {};\r\n\r\n\t\tif (this.props.left && this.isDrawer('left')) {\r\n\t\t\tcontentBarProps.left = {\r\n\t\t\t\tIcon: this.props.left.Icon,\r\n\t\t\t\topen: this.state.leftDrawerOpen,\r\n\t\t\t\tonClick: this.toggleDrawer('left')\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (this.props.right && this.isDrawer('right')) {\r\n\t\t\tcontentBarProps.right = {\r\n\t\t\t\tIcon: this.props.right.Icon,\r\n\t\t\t\topen: this.state.rightDrawerOpen,\r\n\t\t\t\tonClick: this.toggleDrawer('right')\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn {this.props.bar.content} ;\r\n\t}\r\n\r\n\tgetPanelOrDrawer(side)\r\n\t{\r\n\t\tif (!this.props[side]) { return null; }\r\n\t\treturn this.isDrawer(side) ? this.getDrawer(side) : this.getPanel(side);\r\n\t}\r\n\r\n\tgetDrawer(side)\r\n\t{\r\n\t\tconst drawerProps = {\r\n\t\t\topen: this.state[`${side}DrawerOpen`],\r\n\t\t\tanchor: side\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.props[side].content}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tgetPanel(side)\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\t{this.props[side].content}\r\n\t\t\t\t \r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tget ContentPanel()\r\n\t{\r\n\t\treturn {this.props.centre.content} ;\r\n\t}\r\n}\r\n\r\nDesktopExamLayout.propTypes = {\r\n\tbar: PropTypes.shape({\r\n\t\tcontent: PropTypes.node\r\n\t}),\r\n\tleft: PropTypes.shape({\r\n\t\tcontent: PropTypes.node,\r\n\t\tprioritize: PropTypes.bool,\r\n\t\tIcon: PropTypes.oneOfType([\r\n\t\t\tPropTypes.instanceOf(React.Component),\r\n\t\t\tPropTypes.func\r\n\t\t])\r\n\t}),\r\n\tcentre: PropTypes.shape({\r\n\t\tcontent: PropTypes.node\r\n\t}),\r\n\tright: PropTypes.shape({\r\n\t\tcontent: PropTypes.node,\r\n\t\tprioritize: PropTypes.bool,\r\n\t\tIcon: PropTypes.oneOfType([\r\n\t\t\tPropTypes.instanceOf(React.Component),\r\n\t\t\tPropTypes.func\r\n\t\t])\r\n\t})\r\n}\r\n\r\n\r\n// DesktopExamLayout (connected to windowSizeListener)\r\n// -------------------------------------------------------------\r\n\r\nDesktopExamLayout = withWindowSizeListener(DesktopExamLayout);\r\n\r\n\r\n// Export\r\n// -------------------------------------------------------------\r\nexport {DesktopExamLayout}","\r\n// Responsibility:\r\n// Render a Component differently depending on the view we're in\r\n\r\n\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Hidden from '@material-ui/core/Hidden'\r\n\r\n// react\r\nimport {Callback} from 'components/functional/callback'\r\n\r\n\r\n// Hidden breakpoints:\r\n// xs: mobile\r\n// sm: tablet\r\n// mdUp: desktop\r\n\r\n\r\nconst EMPTY_STATE = {\r\n\tmobile: false,\r\n\ttablet: false,\r\n\tdesktop: false\r\n}\r\n\r\nclass ViewSwitcher extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.state = {mobile: false, tablet: false, desktop: true};\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tconst setView = (view) => {\r\n\t\t\tconst newState = {...EMPTY_STATE};\r\n\t\t\tnewState[view] = true;\r\n\t\t\tthis.setState(newState);\r\n\t\t}\r\n\r\n\t\tthis.setMobileView = () => setView('mobile');\r\n\t\tthis.setTabletView = () => setView('tablet');\r\n\t\tthis.setDesktopView = () => setView('desktop');\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tconst nonTablet = ['xs', 'md', 'lg', 'xl'];\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t \r\n\t\t\t\t \r\n\t\t\t\t \r\n\t\t\t\t{this.View}\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tget View()\r\n\t{\r\n\t\treturn this.state.mobile ? this.props.getMobileView() :\r\n\t\t\t\t\t this.state.tablet ? this.props.getDesktopView() : \t\t\t\t\t\t\t\t\t\t\t// NOTE: Change this to 'getTabletView' when we start implementing for tablet screens!!!\r\n\t\t\t\t\t this.props.getDesktopView();\r\n\t}\r\n\r\n\tgetInitialView()\r\n\t{\r\n\t\treturn this.getDesktopView ? 'desktop' :\r\n\t\t\t\t\t this.getTabletView ? 'tablet' :\r\n\t\t\t\t\t this.getMobileView ? 'mobile' : undefined;\r\n\t}\r\n}\r\n\r\nViewSwitcher.propTypes = {\r\n\tgetMobileView: PropTypes.func.isRequired,\r\n\t// getTabletView: PropTypes.func.isRequired, \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// NOTE: Uncomment this when we start implementing for tablet screens!!!\r\n\tgetDesktopView: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nexport {ViewSwitcher}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {MobileExamLayout} from './mobile'\r\nimport {DesktopExamLayout} from './desktop'\r\nimport {ViewSwitcher} from 'components/functional/view-switcher'\r\n\r\n\r\n// Size = 100% of PageWrapper\r\n// Flex-align children along the horizontal axis\r\nconst ExamLayoutContainer = ({children}) =>\r\n{\r\n\tconst divStyle = {\r\n\t\tdisplay: 'flex',\r\n\t\talignItems: 'stretch',\r\n\t\twidth: '100%',\r\n\t\theight: '100%'\r\n\t}\r\n\r\n\treturn {children}
;\r\n}\r\n\r\n\r\nconst ExamLayout = (props) =>\r\n{\r\n\tconst viewSwitcherProps = {\r\n\t\tgetMobileView: () => (\r\n\t\t\t \r\n\t\t),\r\n\t\tgetDesktopView: () => (\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t \r\n\t);\r\n}\r\n\r\nExamLayout.propTypes = {\r\n\tbar: PropTypes.shape({\r\n\t\tcontent: PropTypes.node\r\n\t}),\r\n\tleft: PropTypes.shape({\r\n\t\tcontent: PropTypes.node,\r\n\t\tprioritize: PropTypes.bool,\r\n\t\ticon: PropTypes.instanceOf(React.Component)\r\n\t}),\r\n\tcentre: PropTypes.shape({\r\n\t\tcontent: PropTypes.node\r\n\t}),\r\n\tright: PropTypes.shape({\r\n\t\tcontent: PropTypes.node,\r\n\t\tprioritize: PropTypes.bool,\r\n\t\ticon: PropTypes.instanceOf(React.Component)\r\n\t})\r\n}\r\n\r\n\r\nexport {ExamLayout}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n\r\nlet ContentBarTitle = ({children, classes}) => (\r\n\t\r\n\t\t{children}\r\n\t \r\n)\r\n\r\nContentBarTitle.propTypes = {\r\n\tclasses: PropTypes.object.isRequired,\r\n\tchildren: PropTypes.string.isRequired\r\n}\r\n\r\n\r\nconst styles = ({palette}) => ({\r\n\ttext: {\r\n\t\tflexGrow: 1,\r\n\t\tcolor: palette.background.contrastText\r\n\t}\r\n})\r\n\r\nContentBarTitle = withStyles(styles)(ContentBarTitle);\r\n\r\n\r\nexport {ContentBarTitle}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {ContentBarTitle} from 'components/pages/exam/_layout/content/bar/title'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst examInstructionsMessageId = MESSAGE_IDS.EXAM.INSTRUCTIONS;\r\n\r\n\r\nlet IntroTitle = ({messages}) => (\r\n\t\r\n\t\t{messages[examInstructionsMessageId]}\r\n\t \r\n)\r\n\r\nIntroTitle.propTypes = {\r\n\tmessages: PropTypes.shape({\r\n\t\t[examInstructionsMessageId]: PropTypes.string.isRequired\r\n\t})\r\n}\r\n\r\n\r\nIntroTitle = withMessages(IntroTitle);\r\n\r\n\r\nexport {IntroTitle}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport IconButton from '@material-ui/core/IconButton'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n\r\n// MobileActionButton (not connected to styles)\r\n// ----------------------------------------------------------------------\r\n\r\nlet MobileActionButton = (props) =>\r\n{\r\n\tconst {Icon, children, classes, color, ...buttonProps} = props;\r\n\r\n\tconst colorClass = color === 'primary' ? classes.primaryBackground : \r\n\t\t\t\t\t\t\t\t\t\t color === 'secondary' ? classes.secondaryBackground : \"\";\r\n\r\n\tconst iconButtonProps = {\r\n\t\tclassName: `${classes.button} ${colorClass}`,\r\n\t\tclasses: {\r\n\t\t\tdisabled: classes.buttonDisabled\r\n\t\t},\r\n\t\t...buttonProps\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\nMobileActionButton.propTypes = {\r\n\tIcon: PropTypes.oneOfType([\r\n\t\tPropTypes.instanceOf(React.Component),\r\n\t\tPropTypes.func\r\n\t]).isRequired,\r\n\tclasses: PropTypes.object.isRequired,\r\n\tchildren: PropTypes.node.isRequired\r\n\t// remaining props are spread to IconButton component\r\n}\r\n\r\nMobileActionButton.defaultProps = {\r\n\tcolor: 'primary'\r\n}\r\n\r\n\r\n// MobileActionButton (not connected to styles)\r\n// ----------------------------------------------------------------------\r\n\r\nconst styles = ({palette, spacing}) => ({\r\n\tbutton: {\r\n\t\tmarginLeft: spacing.unit,\r\n\t\tmarginRight: spacing.unit,\r\n\t\tpadding: spacing.unit / 2\r\n\t},\r\n\tprimaryBackground: {\r\n\t\tbackgroundColor: palette.primary.main + \"!important\",\r\n\t\tcolor: palette.primary.contrastText + \"!important\"\r\n\t},\r\n\tsecondaryBackground: {\r\n\t\tbackgroundColor: palette.secondary.main + \"!important\",\r\n\t\tcolor: palette.secondary.contrastText + \"!important\"\r\n\t},\r\n\tbuttonDisabled: {\r\n\t\tbackgroundColor: palette.background.light + \"!important\",\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t}\r\n});\r\n\r\nMobileActionButton = withStyles(styles)(MobileActionButton);\r\n\r\n\r\n// Export\r\n// ----------------------------------------------------------------------\r\nexport {MobileActionButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Button from '@material-ui/core/Button'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n\r\n// DesktopActionButton (not connected to styles)\r\n// ----------------------------------------------------------------------\r\n\r\nlet DesktopActionButton = (props) =>\r\n{\r\n\tconst {Icon, alignment, classes, children, ...buttonProps} = props;\r\n\r\n\tconst buttonComponentProps = {\r\n\t\tclassName: classes.button,\r\n\t\tclasses: {disabled: classes.buttonDisabled},\r\n\t\t...buttonProps\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{alignment === 'left' && }\r\n\t\t\t{children}\r\n\t\t\t{alignment === 'right' && }\r\n\t\t \r\n\t);\r\n}\r\n\r\nDesktopActionButton.propTypes = {\r\n\tIcon: PropTypes.oneOfType([\r\n\t\tPropTypes.instanceOf(React.Component),\r\n\t\tPropTypes.func\r\n\t]).isRequired,\r\n\talignment: PropTypes.string.isRequired,\r\n\tchildren: PropTypes.node.isRequired,\r\n\tclasses: PropTypes.object.isRequired\r\n\t// remaining props are spread to Button component\r\n}\r\n\r\nDesktopActionButton.defaultProps = {\r\n\tcolor: 'primary',\r\n\tsize: 'small',\r\n\tvariant: 'contained',\r\n\talignment: 'left'\r\n}\r\n\r\n\r\n// DesktopActionButton (connected to styles)\r\n// ----------------------------------------------------------------------\r\n\r\nconst styles = (theme) =>\r\n{\r\n\tconst spacing = theme.spacing.unit;\r\n\tconst {palette} = theme;\r\n\r\n\treturn {\r\n\t\tbutton: {\r\n\t\t\tmarginLeft: spacing,\r\n\t\t\tmarginRight: spacing\r\n\t\t},\r\n\t\tbuttonDisabled: {\r\n\t\t\tbackgroundColor: palette.background.light + \"!important\",\r\n\t\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t\t},\r\n\t\ticonLeft: {\r\n\t\t\tmarginRight: spacing\r\n\t\t},\r\n\t\ticonRight: {\r\n\t\t\tmarginLeft: spacing\r\n\t\t}\r\n\t}\r\n}\r\n\r\nDesktopActionButton = withStyles(styles)(DesktopActionButton);\r\n\r\n\r\n// Export\r\n// ----------------------------------------------------------------------\r\nexport {DesktopActionButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {MobileActionButton} from './mobile'\r\nimport {DesktopActionButton} from './desktop'\r\nimport {ViewSwitcher} from 'components/functional/view-switcher'\r\n\r\n\r\nclass ActionButton extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.getMobileActionButton = () => {\r\n\t\t\tconst buttonProps = {...this.props, alignment: undefined};\r\n\t\t\treturn ;\t\r\n\t\t}\r\n\r\n\t\tthis.getDesktopActionButton = () => {\r\n\t\t\treturn ;\t\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tconst props = {\r\n\t\t\tgetMobileView: this.getMobileActionButton,\r\n\t\t\tgetDesktopView: this.getDesktopActionButton\r\n\t\t}\r\n\r\n\t\treturn ;\r\n\t}\r\n}\r\n\r\nActionButton.propTypes = {\r\n\tIcon: PropTypes.oneOfType([\r\n\t\tPropTypes.instanceOf(React.Component),\r\n\t\tPropTypes.func\r\n\t]).isRequired,\r\n\talignment: PropTypes.string.isRequired,\r\n\tchildren: PropTypes.node.isRequired\r\n\t// remaining props are spread to child component\r\n}\r\n\r\nActionButton.defaultProps = {\r\n\tcolor: 'primary',\r\n\tdisabled: false\r\n}\r\n\r\n\r\nexport {ActionButton}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\n\r\n// material-ui\r\nimport ArrowForwardIcon from \"@material-ui/icons/ArrowForward\";\r\n\r\n// react\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { ActionButton } from \"components/layout/action_button/button\";\r\n\r\n// other\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nconst startMessageId = MESSAGE_IDS.GENERAL.START;\r\nconst resumeMessageId = MESSAGE_IDS.GENERAL.RESUME;\r\n\r\nlet StartExamButton = ({ onClick, messages, resume, disabled }) => {\r\n const messageId = resume ? resumeMessageId : startMessageId;\r\n\r\n return (\r\n \r\n {messages[messageId]}\r\n \r\n );\r\n};\r\n\r\nStartExamButton.propTypes = {\r\n messages: PropTypes.shape({\r\n [startMessageId]: PropTypes.string.isRequired,\r\n }),\r\n onClick: PropTypes.func.isRequired,\r\n};\r\n\r\nStartExamButton = withMessages(StartExamButton);\r\n\r\nexport { StartExamButton };\r\n","\r\nconst getIntroText = (messageData) => messageData.get('introText');\r\nconst getOutroText = (messageData) => messageData.get('outroText');\r\nconst getProctoredOutroText = (messageData) => messageData.get('proctoredOutroText');\r\n\r\nexport {getIntroText, getOutroText, getProctoredOutroText}","\r\n// npm\r\nimport {connect} from 'react-redux'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getMessageData} from 'redux/reducers/exam/selectors'\r\nimport {getIntroText} from 'redux/reducers/exam/messages/selectors'\r\n\r\n// react\r\nimport {TextPanel} from '../_presentation/text-panel'\r\n\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\ttext: getIntroText(getMessageData(getExamData(store)))\r\n})\r\n\r\nconst InstructionsPanel = connect(mapStoreToProps)(TextPanel);\r\n\r\n\r\nexport {InstructionsPanel}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {withRouter} from 'react-router-dom'\r\n\r\n// material-ui\r\nimport HomeIcon from '@material-ui/icons/Home'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {ActionButton} from 'components/layout/action_button/button'\r\n\r\n// other\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst returnFromExamMessageId = MESSAGE_IDS.EXAM.RETURN_HOME;\r\n\r\n\r\nconst iconStyles = ({palette}) => {\r\n\treturn {\r\n\t\t\ttext: {\r\n\t\t\tcolor: palette.secondary.contrastText + \"!important\"\r\n\t\t}\r\n\t}\r\n}\r\n\r\nlet MyHomeIcon = ({classes, ...props}) => {\r\n\treturn ;\r\n}\r\n\r\nMyHomeIcon = withStyles(iconStyles)(MyHomeIcon);\r\n\r\n\r\n// ReturnToSchedulesButton (not connected to router/messages)\r\n// --------------------------------------------------------------------------\r\nlet ReturnToSchedulesButton = ({history, classes, messages}) =>\r\n{\r\n\tconst onClick = () => history.replace('/schedules');\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{messages[returnFromExamMessageId]}\r\n\t\t \r\n\t)\r\n}\r\n\r\nReturnToSchedulesButton.propTypes = {\r\n\thistory: PropTypes.object.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[returnFromExamMessageId]: PropTypes.string.isRequired\r\n\t})\r\n}\r\n\r\nReturnToSchedulesButton = withRouter(ReturnToSchedulesButton);\r\nReturnToSchedulesButton = withMessages(ReturnToSchedulesButton);\r\n\r\n\r\n// Export\r\n// --------------------------------------------------------------------------\r\nexport {ReturnToSchedulesButton}","const openInFullScreen = (onlyIfNotFullScreen = false) => {\r\n if (onlyIfNotFullScreen) {\r\n if (document.fullscreenElement) return;\r\n }\r\n\r\n const elem = document.documentElement;\r\n\r\n if (elem.requestFullscreen) {\r\n elem.requestFullscreen();\r\n } else if (elem.webkitRequestFullscreen) {\r\n /* Safari */\r\n elem.webkitRequestFullscreen();\r\n } else if (elem.msRequestFullscreen) {\r\n /* IE11 */\r\n elem.msRequestFullscreen();\r\n }\r\n};\r\n\r\nconst closeFullScreen = () => {\r\n if (document.fullscreenElement) {\r\n if (document.exitFullscreen) {\r\n document.exitFullscreen();\r\n } else if (document.webkitExitFullscreen) {\r\n /* Safari */\r\n document.webkitExitFullscreen();\r\n } else if (document.msExitFullscreen) {\r\n /* IE11 */\r\n document.msExitFullscreen();\r\n }\r\n }\r\n};\r\n\r\nexport { openInFullScreen, closeFullScreen };\r\n","\r\n// npm\r\nimport React, { Component } from 'react';\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// react (layout)\r\nimport {ExamLayout} from '../../../../exam/_layout/layout'\r\nimport {Align} from 'components/layout/align'\r\n\r\n// react (concrete)\r\nimport {IntroTitle} from './intro-title'\r\nimport {StartExamButton} from './start-exam-button'\r\nimport {InstructionsPanel} from '../../../../exam/running/instructions-panel'\r\nimport {ReturnToSchedulesButton} from './return-to-schedules-button'\r\n\r\n// redux (selectors)\r\nimport {getGuid, getShowExamInFullScreen,} from 'redux/reducers/exam/selectors'\r\nimport {getExamData, getSchedulesData} from 'redux/reducers/selectors'\r\nimport {getVersion, hasScheduleCommenced,} from 'redux/reducers/schedules/schedule/selectors'\r\nimport {getScheduleDataByExamGuid} from 'redux/reducers/schedules/selectors'\r\nimport {getSettingsData} from 'redux/reducers/selectors'\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getProctoringMode} from 'redux/reducers/settings/client/selectors'\r\n\r\nimport { openInFullScreen } from \"components/pages/_exam/initialized/running/leave_resume_exam/full_screen/helper\"\r\n\r\nimport { SET_INITIAL_FULL_SCREEN } from \"redux/reducers/exam/action-types\";\r\n\r\n\r\n// ExamIntroPage (not connected to store)\r\n// -----------------------------------------------------------------\r\n\r\nclass ExamIntroPage extends Component {\r\n\tconstructor(props) {\r\n super(props);\r\n\r\n this.handleExamStart = this.handleExamStart.bind(this);\r\n this.handleFullScreenChange = this.handleFullScreenChange.bind(this);\r\n }\r\n\r\n componentDidMount() {\r\n const { showExamInFullScreen } = this.props;\r\n\r\n if (showExamInFullScreen) {\r\n document.addEventListener(\r\n \"fullscreenchange\",\r\n this.handleFullScreenChange\r\n );\r\n }\r\n }\r\n\r\n componentWillUnmount() {\r\n const { showExamInFullScreen } = this.props;\r\n\r\n if (showExamInFullScreen) {\r\n document.removeEventListener(\r\n \"fullscreenchange\",\r\n this.handleFullScreenChange\r\n );\r\n }\r\n }\r\n\r\n handleFullScreenChange() {\r\n const { onExamStart } = this.props;\r\n if (document.fullscreenElement) {\r\n onExamStart();\r\n }\r\n }\r\n\r\n handleExamStart() {\r\n const { showExamInFullScreen, onExamStart, setInitialFullScreen } =\r\n this.props;\r\n\r\n if (showExamInFullScreen) {\r\n setInitialFullScreen(document.fullscreenElement);\r\n openInFullScreen(true);\r\n } else {\r\n onExamStart();\r\n }\r\n }\r\n\r\n\trender() { \r\n\t\tconst {showReturnButton, resume} = this.props;\r\n\r\n\t\tconst examLayoutProps = {\r\n\t\t\tbar: {\r\n\t\t\t\tcontent: (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t\t{showReturnButton && !resume ? : null}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t \r\n\t\t\t\t)\r\n\t\t\t},\r\n\t\t\tcentre: {\r\n\t\t\t\tcontent: (\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t \r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\t\r\n\t\treturn ;\r\n\t}\r\n}\r\n\r\nExamIntroPage.propTypes = {\r\n\tonExamStart: PropTypes.func.isRequired,\r\n\tshowReturnButton: PropTypes.bool.isRequired\r\n}\r\n\r\n\r\n// ExamIntroPage (connected to store)\r\n// -----------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\tconst examData = getExamData(store);\r\n\tconst examGuid = getGuid(examData);\r\n\r\n\tconst schedulesData = getSchedulesData(store);\r\n\tconst scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n\tconst proctoringMode = getProctoringMode(getClientSettingsData(getSettingsData(store)));\r\n\tconst showReturnButton = proctoringMode == 1 ? false : !getVersion(scheduleData);\r\n\r\n\tconst showExamInFullScreen = getShowExamInFullScreen(examData);\r\n const resume = showExamInFullScreen\r\n ? hasScheduleCommenced(scheduleData)\r\n : false;\r\n\r\n\treturn {\r\n\t\tshowReturnButton,\r\n\t\tshowExamInFullScreen,\r\n resume,\t\t\r\n\t}\r\n}\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setInitialFullScreen: (value) => {\r\n dispatch({\r\n type: SET_INITIAL_FULL_SCREEN,\r\n value,\r\n });\r\n },\r\n});\r\n\r\nExamIntroPage = connect(mapStoreToProps, mapDispatchToProps)(ExamIntroPage);\r\n\r\n\r\n// ExamIntroPage (EXPORT)\r\n// -----------------------------------------------------------------\r\nexport {ExamIntroPage}","const containsId = (mappingData) => (id) => mappingData.includes(id);\r\n\r\nexport {containsId}","import {containsId} from './mapping/selectors'\r\n\r\n\r\n\r\n// Selector functions\r\n// ---------------------------------------------------------------------------\r\n\r\nconst getMappingDataById = (mappingsData) => (id) => {\r\n\treturn mappingsData.get(id);\r\n}\r\n\r\nconst getChildrenForId = (mappingsData) => (id) => {\r\n\tconst mappingData = getMappingDataById(mappingsData)(id);\r\n\treturn mappingData ? mappingData.toArray() : [];\r\n}\r\n\r\nconst getParentSectionId = (mappingsData) => (childId) => {\r\n\treturn mappingsData.findKey(mapping => containsId(mapping)(childId));\r\n}\r\n\r\n\r\nexport {\r\n\tgetMappingDataById,\r\n\tgetChildrenForId,\r\n\tgetParentSectionId\r\n}","\r\n// redux (selectors)\r\nimport * as SELECTORS from './selectors'\r\n\r\n\r\nconst traverseParentIds = (mappingsData) => function*(childId)\r\n{\r\n\tconst getParentSectionId = SELECTORS.getParentSectionId(mappingsData);\r\n\tlet nextParentId = getParentSectionId(childId);\r\n\r\n\twhile (nextParentId && nextParentId !== 'root')\r\n\t{\r\n\t\tyield nextParentId;\r\n\t\tnextParentId = getParentSectionId(nextParentId);\r\n\t}\r\n}\r\n\r\nconst traverseDepthFirst = (mappingsData) => function*(id)\r\n{\r\n\tconst childIds = SELECTORS.getChildrenForId(mappingsData)(id);\r\n\tif (!childIds) { return; }\r\n\r\n\tfor (let i = 0; i < childIds.length; i++)\r\n\t{\r\n\t\tconst childId = childIds[i];\r\n\t\tyield childId;\r\n\t\tyield* traverseDepthFirst(mappingsData)(childId);\r\n\t}\r\n}\r\n\r\n\r\nexport {traverseParentIds, traverseDepthFirst}","\r\n// redux (helpers)\r\nimport {traverseParentIds, traverseDepthFirst} from './mappings/helpers'\r\n\r\n// redux (selectors)\r\nimport * as SELECTORS from './selectors'\r\nimport {getSectionDataById} from './sections/selectors'\r\n\r\n\r\nconst traverseParents = (contentData) => function*(childId)\r\n{\r\n\tconst sectionsData = SELECTORS.getSectionsData(contentData);\r\n\tconst mappingsData = SELECTORS.getMappingsData(contentData);\r\n\tconst _traverseParentIds = traverseParentIds(mappingsData);\r\n\tconst _getSectionDataById = getSectionDataById(sectionsData);\r\n\tfor (let parentId of _traverseParentIds(childId)) { \r\n\t\tyield _getSectionDataById(parentId);\r\n\t}\r\n}\r\n\r\nconst traverseChildIds = (contentData) => function*(id)\r\n{\r\n\tconst mappingsData = SELECTORS.getMappingsData(contentData);\r\n\tconst _traverseChildIds = traverseDepthFirst(mappingsData);\r\n\tfor (let childId of _traverseChildIds(id)) { yield childId; }\r\n}\r\n\r\n\r\nexport {traverseParents, traverseChildIds}","// INFORMATION:\r\n\r\n// Simple selectors (selectors.js):\r\n// These work on direct properties of an immutable object in order to return\r\n// the resulting data\r\n\r\n// Complex selectors (complex-selectors.js): \r\n// These utilize selectors from child reducers in order to find the data that \r\n// must be returned\r\n\r\nimport check from 'check-types'\r\n\r\nimport * as CONTENT_SELECTORS from './selectors'\r\nimport * as MAPPINGS_SELECTORS from './mappings/selectors'\r\nimport * as QUESTIONS_SELECTORS from './questions/selectors'\r\nimport * as SECTION_SELECTORS from './sections/section/selectors'\r\nimport * as QUESTION_SELECTORS from './questions/question/selectors'\r\n\r\nimport {traverseParents, traverseChildIds} from './helpers'\r\n\r\n\r\n\r\nconst isQuestion = (contentData) => (id) =>\r\n{\r\n\tconst questionsData = CONTENT_SELECTORS.getQuestionsData(contentData);\r\n\treturn !!QUESTIONS_SELECTORS.getQuestionDataById(questionsData)(id);\t\r\n}\r\n\r\n\r\nconst getResourceUrl = (contentData) =>\r\n{\r\n\t// 1. Check current question for resource url\r\n\tconst questionsData = CONTENT_SELECTORS.getQuestionsData(contentData);\r\n\tconst currentQuestionId = CONTENT_SELECTORS.getCurrentQuestionId(contentData);\r\n\tconst currentQuestionData = QUESTIONS_SELECTORS.getQuestionDataById(questionsData)(currentQuestionId);\r\n\tconst questionResourceUrl = QUESTION_SELECTORS.getResourceUrl(currentQuestionData);\r\n\tif (!!questionResourceUrl) { return questionResourceUrl; }\r\n\r\n\t// 2. Traverse parents to find resource url\r\n\tfor (let parentSectionData of traverseParents(contentData)(currentQuestionId))\r\n\t{\r\n\t\tconst sectionResourceUrl = SECTION_SELECTORS.getResourceUrl(parentSectionData);\r\n\t\tif (!!sectionResourceUrl) { return sectionResourceUrl; }\r\n\t}\r\n\r\n\treturn CONTENT_SELECTORS.getResourceUrl(contentData);\r\n}\r\n\r\n\r\nconst getPaperPartId = (contentData) => (questionId) =>\r\n{\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId))\r\n\t{\r\n\t\tconst isOneWay = SECTION_SELECTORS.isOneWay(parentSectionData);\r\n\t\tif (isOneWay) { return SECTION_SELECTORS.getSectionId(parentSectionData); }\r\n\t}\r\n}\r\n\r\nconst hasPaperPartUnansweredQuestions = (contentData) => (id) =>\r\n{\r\n\tconst questionsData = CONTENT_SELECTORS.getQuestionsData(contentData);\r\n\tconst _getQuestionData = QUESTIONS_SELECTORS.getQuestionDataById(questionsData);\r\n\r\n\tfor (let childId of traverseChildIds(contentData)(id))\r\n\t{\r\n\t\tif (!isQuestion(contentData)(childId)) { continue; }\r\n\t\tconst questionData = _getQuestionData(childId);\r\n\t\tif (!QUESTION_SELECTORS.isAnswered(questionData)) { return true; }\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\r\nconst getNextPaperPartId = (contentData) => (id) =>\r\n{\r\n\tconst mappingsData = CONTENT_SELECTORS.getMappingsData(contentData);\r\n\tconst parentSectionId = MAPPINGS_SELECTORS.getParentSectionId(mappingsData)(id);\r\n\tconst siblingIds = MAPPINGS_SELECTORS.getChildrenForId(mappingsData)(parentSectionId);\r\n\tconst nextIndex = siblingIds.indexOf(id) + 1;\r\n\treturn nextIndex === siblingIds.length ? null : siblingIds[nextIndex];\r\n}\r\n\r\nconst getFirstQuestionIdInSection = (contentData) => (id) =>\r\n{\r\n\tconst mappingsData = CONTENT_SELECTORS.getMappingsData(contentData);\r\n\tconst childIds = MAPPINGS_SELECTORS.getChildrenForId(mappingsData)(id);\r\n\tconst firstId = childIds[0];\r\n\tif (isQuestion(contentData)(firstId)) { return firstId; }\r\n\treturn getFirstQuestionIdInSection(contentData)(firstId);\r\n}\r\n\r\nconst hasSamePaperPart = (contentData) => (id1, id2) =>\r\n{\r\n\tconst paperPartId1 = getPaperPartId(contentData)(id1);\r\n\tconst paperPartId2 = getPaperPartId(contentData)(id2);\r\n\treturn paperPartId1 === paperPartId2;\r\n}\r\n\r\n\r\nconst getQuestionSectionText = (contentData) => (questionId) =>\r\n{\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId))\r\n\t{\r\n\t\tconst sectionText = SECTION_SELECTORS.getSectionText(parentSectionData);\r\n\t\tif (sectionText) { return sectionText; }\r\n\t}\r\n}\r\n\r\n\r\nconst getQuestionScenarioText = (contentData) => (questionId) =>\r\n{\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId))\r\n\t{\r\n\t\tconst scenarioText = SECTION_SELECTORS.getScenarioText(parentSectionData);\r\n\t\tif (scenarioText) { return scenarioText; }\r\n\t}\r\n}\r\n\r\n\r\nconst getQuestionNoInSection = (contentData) => (sectionId, questionId) =>\r\n{\r\n\tlet questionNumber = 0;\r\n\r\n\tfor (let childId of traverseChildIds(contentData)(sectionId)) {\r\n\t\tif (isQuestion(contentData)(childId)) { questionNumber++; }\r\n\t\tif (childId === questionId) { return questionNumber; }\r\n\t}\r\n\r\n\tthrow \"Question doesn't appear to be contained in this section\";\r\n}\r\n\r\n\r\nconst getQuestionCountInSection = (contentData) => (sectionId) =>\r\n{\r\n\tlet count = 0;\r\n\r\n\tfor (let childId of traverseChildIds(contentData)(sectionId)) {\r\n\t\tif (isQuestion(contentData)(childId)) { count++; }\r\n\t}\r\n\r\n\treturn count;\r\n}\r\n\r\n\r\nconst getSiblingQuestionId = (contentData) =>\r\n{\r\n\tconst currentQuestionId = CONTENT_SELECTORS.getCurrentQuestionId(contentData);\r\n\tconst questionsData = CONTENT_SELECTORS.getQuestionsData(contentData);\r\n\tconst getQuestionDataById = QUESTIONS_SELECTORS.getQuestionDataById(questionsData);\r\n\tconst getQuestionDataByNumber = QUESTIONS_SELECTORS.getQuestionDataByNumber(questionsData);\r\n\r\n\treturn (questionId=currentQuestionId, direction) => {\r\n\t\tconst questionData = getQuestionDataById(questionId);\r\n\t\tconst questionNumber = QUESTION_SELECTORS.getNumber(questionData);\r\n\t\tconst questionCount = QUESTIONS_SELECTORS.getQuestionCount(questionsData);\r\n\r\n\t\tlet siblingNumber = questionNumber + direction;\r\n\t\tconst {isDisabled, getId} = QUESTION_SELECTORS;\r\n\r\n\t\twhile (siblingNumber > 0 && siblingNumber <= questionCount) {\r\n\t\t\tconst siblingData = getQuestionDataByNumber(siblingNumber);\r\n\t\t\tconst siblingId = getId(siblingData);\r\n\r\n\t\t\tif (!hasSamePaperPart(contentData)(questionId, siblingId)) { return undefined; }\r\n\t\t\tif (!isDisabled(siblingData)) { return siblingId; }\r\n\r\n\t\t\tsiblingNumber += direction;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\r\nconst getNextQuestionId = (contentData, questionId) => {\r\n\treturn getSiblingQuestionId(contentData)(questionId, 1);\r\n}\r\n\r\n\r\nconst getPreviousQuestionId = (contentData, questionId) => {\r\n\treturn getSiblingQuestionId(contentData)(questionId, -1);\r\n}\r\n\r\n\r\nconst getResumedQuestionId = (contentData) =>\r\n{\r\n\tconst questionsData = CONTENT_SELECTORS.getQuestionsData(contentData);\r\n\tconst resumedNumber = CONTENT_SELECTORS.getResumedQuestionNumber(contentData);\r\n\tconst getQuestionDataByNumber = QUESTIONS_SELECTORS.getQuestionDataByNumber(questionsData);\r\n\r\n\tconst resumedQuestionData = getQuestionDataByNumber(resumedNumber);\r\n\tconst resumedId = QUESTION_SELECTORS.getId(resumedQuestionData);\r\n \r\n\tconst questionDisabled = QUESTION_SELECTORS.isDisabled(resumedQuestionData);\r\n\treturn questionDisabled ? getNextQuestionId(contentData, resumedId) : resumedId;\r\n}\r\n\r\n\r\nconst hasUnansweredQuestions = (contentData) =>\r\n{\r\n\tconst questionsData = CONTENT_SELECTORS.getQuestionsData(contentData);\r\n\tconst unansweredQuestion = questionsData.find(questionData => {\r\n\t\treturn !QUESTION_SELECTORS.isAnswered(questionData) && !QUESTION_SELECTORS.isDisabled(questionData);\r\n\t});\r\n\treturn !!unansweredQuestion;\r\n}\r\n\r\n\r\nconst requiresWorkings = (contentData, questionId) =>\r\n{\r\n\tconst questionsData = CONTENT_SELECTORS.getQuestionsData(contentData);\r\n\tconst questionData = QUESTIONS_SELECTORS.getQuestionDataById(questionsData)(questionId);\r\n\tif (QUESTION_SELECTORS.requiresWorkings(questionData)) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId))\r\n\t{\r\n\t\tconst hasWorkings = SECTION_SELECTORS.requiresWorkings(parentSectionData);\r\n\t\tif (hasWorkings) { return hasWorkings; }\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\r\n\r\nconst requiresCalculator = (contentData, questionId) =>\r\n{\r\n\r\n\tif (CONTENT_SELECTORS.getRequiresCalculator(contentData)){\r\n\t\treturn true;\r\n\t}\r\n\r\n\tconst questionsData = CONTENT_SELECTORS.getQuestionsData(contentData);\r\n\tconst questionData = QUESTIONS_SELECTORS.getQuestionDataById(questionsData)(questionId);\r\n\r\n\tif (QUESTION_SELECTORS.requiresCalculator(questionData)) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId)) {\r\n\t\tconst hasCalculator = SECTION_SELECTORS.requiresCalculator(parentSectionData);\r\n\t\tif (hasCalculator) { return hasCalculator; }\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\r\n\r\nconst getReadingTimeRemaining = (contentData, questionId) =>\r\n{\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId)) {\r\n\t\tconst readingTimeRemaining = SECTION_SELECTORS.getReadingTimeRemaining(parentSectionData);\r\n\t\tif (check.assigned(readingTimeRemaining)) { return readingTimeRemaining; }\r\n\t}\r\n}\r\n\r\nconst getAnsweringTimeRemaining = (contentData, questionId) =>\r\n{\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId)) {\r\n\t\tconst answeringTimeRemaining = SECTION_SELECTORS.getSectionTimeRemaining(parentSectionData);\r\n\t\tif (check.assigned(answeringTimeRemaining)) { return answeringTimeRemaining; }\r\n\t}\r\n}\r\n\r\nconst getReadingTime = (contentData, questionId) =>\r\n{\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId)) {\r\n\t\tconst readingTime = SECTION_SELECTORS.getReadingTime(parentSectionData);\r\n\t\tif (check.assigned(readingTime)) { return readingTime; }\r\n\t}\r\n}\r\n\r\nconst getAnsweringTime = (contentData, questionId) =>\r\n{\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId)) {\r\n\t\tconst answeringTime = SECTION_SELECTORS.getSectionTime(parentSectionData);\r\n\t\tif (check.assigned(answeringTime)) { return answeringTime; }\r\n\t}\r\n}\r\n\r\nconst getWarningTime = (contentData, questionId) =>\r\n{\r\n\tfor (let parentSectionData of traverseParents(contentData)(questionId)) {\r\n\t\tconst warningTime = SECTION_SELECTORS.getWarningTime(parentSectionData);\r\n\t\tif (check.assigned(warningTime)) { return warningTime; }\r\n\t}\r\n}\r\n\r\n\r\nexport {\r\n\tisQuestion,\r\n\tgetResourceUrl,\r\n\tgetQuestionSectionText,\r\n\tgetQuestionScenarioText,\r\n\tgetQuestionNoInSection,\r\n\tgetNextQuestionId,\r\n\tgetPreviousQuestionId,\r\n\tgetResumedQuestionId,\r\n\thasUnansweredQuestions,\r\n \r\n\tgetPaperPartId,\r\n\thasPaperPartUnansweredQuestions,\r\n\tgetNextPaperPartId,\r\n\tgetFirstQuestionIdInSection,\r\n\tgetQuestionCountInSection,\r\n\thasSamePaperPart,\r\n\trequiresWorkings,\r\n\trequiresCalculator,\r\n\r\n\tgetReadingTime,\r\n\tgetReadingTimeRemaining,\r\n\tgetAnsweringTime,\r\n\tgetAnsweringTimeRemaining,\r\n\tgetWarningTime\r\n}","\r\nimport {SET_CURRENT_QUESTION_ID} from 'redux/reducers/exam/content/action-types'\r\nimport {SET_PUBLIC_EXAM_USER_NAME} from 'redux/reducers/session/user/action-types'\r\n\r\n\r\nconst setCurrentQuestionId = (value) => ({\r\n\ttype: SET_CURRENT_QUESTION_ID,\r\n\tvalue\r\n});\r\n\r\nconst setPublicExamUserName = (value) => ({\r\n\ttype: SET_PUBLIC_EXAM_USER_NAME,\r\n\tvalue\r\n});\r\n\r\n\r\nexport {setCurrentQuestionId, setPublicExamUserName}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport ImmutablePropTypes from 'immutable-prop-types'\r\n\r\n// redux (selectors)\r\nimport {getExamData, getSchedulesData} from 'redux/reducers/selectors'\r\nimport {getQuestionsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getContentData, getGuid} from 'redux/reducers/exam/selectors'\r\nimport {getScheduleDataByExamGuid} from 'redux/reducers/schedules/selectors'\r\nimport {hasScheduleCommenced} from 'redux/reducers/schedules/schedule/selectors'\r\nimport {getResumedQuestionId} from 'redux/reducers/exam/content/complex-selectors'\r\n\r\n// redux (actions)\r\nimport {setCurrentQuestionId} from './actions'\r\n\r\n\r\n// CommencedChecker (not connected to store)\r\n// ----------------------------------------------------------------------------\r\n\r\nclass CommencedChecker extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tconst {schedulesData, examGuid} = this.props;\r\n\t\tconst scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n\r\n\t\tif (hasScheduleCommenced(scheduleData)) {\r\n\t\t\tthis.props.onCommenced();\r\n\t\t}\r\n\t\telse {\r\n\t\t\tthis.props.onNotCommenced();\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nCommencedChecker.propTypes = {\r\n\tonCommenced: PropTypes.func.isRequired,\r\n\tonNotCommenced: PropTypes.func.isRequired,\r\n\texamGuid: PropTypes.string.isRequired,\r\n\tschedulesData: ImmutablePropTypes.list.isRequired\r\n}\r\n\r\n\r\n// CommencedChecker (connected to store)\r\n// ----------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n\tconst contentData = getContentData(getExamData(store));\r\n\t\r\n\treturn {\r\n\t\tinitialQuestionId: getResumedQuestionId(contentData),\r\n\t\texamGuid: getGuid(getExamData(store)),\r\n\t\tschedulesData: getSchedulesData(store)\r\n\t}\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch, ownProps) => ({\r\n\tonCommenced: (initialQuestionId) => () => {\r\n\t\tdispatch(setCurrentQuestionId(initialQuestionId));\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Set up question to resume to!\r\n\t\townProps.onCommenced();\r\n\t},\r\n\tonNotCommenced: (initialQuestionId) => () => {\r\n\t\tdispatch(setCurrentQuestionId(initialQuestionId));\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Set up question to resume to!\r\n\t\townProps.onNotCommenced();\r\n\t}\r\n})\r\n\r\nconst mergeProps = (storeProps, dispatchProps, ownProps) => ({\r\n\texamGuid: storeProps.examGuid,\r\n\tschedulesData: storeProps.schedulesData,\r\n\tonCommenced: dispatchProps.onCommenced(storeProps.initialQuestionId),\r\n\tonNotCommenced: dispatchProps.onNotCommenced(storeProps.initialQuestionId)\r\n});\r\n\r\nconst args = [mapStoreToProps, mapDispatchToProps, mergeProps];\r\nCommencedChecker = connect(...args)(CommencedChecker);\r\n\r\n\r\n// Export\r\n// ----------------------------------------------------------------------------\r\nexport {CommencedChecker}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport {Redirect} from 'react-router-dom'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getCurrentQuestionId} from 'redux/reducers/exam/content/selectors'\r\n\r\n\r\nlet RedirectToExamQuestions = ({examGuid, currentQuestionId}) => (\r\n\t\r\n)\r\n\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\tcurrentQuestionId: getCurrentQuestionId(getContentData(getExamData(store)))\r\n});\r\n\r\nRedirectToExamQuestions = connect(mapStoreToProps)(RedirectToExamQuestions);\r\n\r\n\r\nexport {RedirectToExamQuestions}","// npm\r\nimport React from \"react\";\r\nimport { connect } from \"react-redux\";\r\nimport PropTypes from \"prop-types\";\r\n\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getAppVersion } from \"redux/reducers/settings/app/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\n\r\n// react\r\nimport { LoadingSpinner } from \"components/presentation/loading-spinner\";\r\nimport {detectBrowser} from 'components/pages/app_initialization/initializing/validating_app_settings/browser_checker/detect-browser'\r\nimport { activityLogger } from \"libs/activity_logger/activity-logger\";\r\n\r\nclass StartActivityLogger extends React.Component {\r\n componentDidMount() {\r\n const { activityType, onComplete, appVersion } = this.props;\r\n const {browser, version} = detectBrowser();\r\n const payload = {\r\n appVersion,\r\n browser, \r\n version\r\n }\r\n\r\n activityLogger.log(activityType, payload);\r\n onComplete();\r\n }\r\n\r\n render() {\r\n return ;\r\n }\r\n}\r\n\r\nStartActivityLogger.propTypes = {\r\n activityType: PropTypes.number.isRequired,\r\n onComplete: PropTypes.func.isRequired,\r\n};\r\n\r\nconst mapStoreToProps = (store) => {\r\n const appSettingsData = getAppSettingsData(getSettingsData(store));\r\n\r\n return {\r\n appVersion: getAppVersion(appSettingsData),\r\n };\r\n};\r\n\r\nStartActivityLogger = connect(mapStoreToProps)(StartActivityLogger);\r\n\r\nexport { StartActivityLogger };\r\n","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\n\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getSettingsData, getExamData } from \"redux/reducers/selectors\";\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport { getIsDownloadablePracticeExam } from \"redux/reducers/settings/client/selectors\";\r\n\r\nimport {getFormRunGuid} from 'redux/reducers/exam/selectors'\r\nimport * as actions from './actions'\r\n\r\nimport TextField from \"@material-ui/core/TextField\";\r\nimport Card from \"@material-ui/core/Card\";\r\nimport CardContent from \"@material-ui/core/CardContent\";\r\nimport Button from \"@material-ui/core/Button\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport IconButton from \"@material-ui/core/IconButton\";\r\nimport ClearIcon from \"@material-ui/icons/Clear\";\r\n\r\n// react\r\nimport { LoadingSpinner } from \"components/presentation/loading-spinner\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\r\n\r\nconst styles = ({ spacing, palette, breakpoints }) => ({\r\n panel: {\r\n backgroundColor: palette.background.light,\r\n [breakpoints.down(800)]: {\r\n width: \"80%\",\r\n },\r\n [breakpoints.up(801)]: {\r\n width: 800,\r\n },\r\n marginTop: spacing.unit * 2,\r\n alignSelf: \"stretch\",\r\n },\r\n root: {\r\n width: \"100%\",\r\n display: \"flex\",\r\n justifyContent: \"center\",\r\n },\r\n inputDiv: {\r\n width: \"calc(100% - 8px)\",\r\n display: \"flex\",\r\n justifyContent: \"flex-end\",\r\n alignItems: \"center\",\r\n marginBottom: spacing.unit*2,\r\n \"&>div:first-child\": {\r\n width: \"100%\",\r\n },\r\n },\r\n buttonDiv: {\r\n width: \"100%\",\r\n display: \"flex\",\r\n justifyContent: \"flex-end\",\r\n },\r\n});\r\n\r\nconst saveUserName = (formRunGuid, userName) => {\r\n return new Promise((resolve) => {\r\n setTimeout(() => {\r\n const errorText = userName.includes('bum') ? 'User name not acceptable' : '';\r\n resolve(errorText);\r\n }, 3000);\r\n });\r\n};\r\n\r\nclass PublicExamPage extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleComplete = this.handleComplete.bind(this);\r\n this.handleChange = this.handleChange.bind(this);\r\n this.handleClear = this.handleClear.bind(this);\r\n this.handleKeyPress = this.handleKeyPress.bind(this);\r\n\r\n this.state = { busy: false, value: \"\", errorText: \"\" };\r\n }\r\n\r\n componentDidMount() {\r\n const { onComplete, isDownloadablePracticeExam } = this.props;\r\n \r\n if (isDownloadablePracticeExam) {\r\n if (this.nameInput) {\r\n this.nameInput.focus();\r\n }\r\n }\r\n else{\r\n onComplete();\r\n }\r\n }\r\n\r\n handleComplete() {\r\n const { value } = this.state;\r\n const { onComplete, formRunGuid, setPublicExamUserName } = this.props;\r\n this.setState({ busy: true }, () => {\r\n setPublicExamUserName(value);\r\n onComplete();\r\n\r\n // saveUserName(formRunGuid, value).then((errorText) => {\r\n // if (errorText && errorText!=='') {\r\n // this.setState({errorText, busy:false})\r\n // }\r\n // else{\r\n // onComplete();\r\n // }\r\n // });\r\n });\r\n }\r\n\r\n handleKeyPress(e){\r\n if(e.keyCode == 13){\r\n this.handleComplete();\r\n } \r\n }\r\n\r\n handleChange(value) {\r\n this.setState({ value });\r\n }\r\n\r\n handleClear() {\r\n this.setState({ value: \"\" });\r\n }\r\n\r\n render() {\r\n const { busy, value, errorText } = this.state;\r\n const { classes, isDownloadablePracticeExam } = this.props;\r\n\r\n if (!isDownloadablePracticeExam || busy) {\r\n return ;\r\n }\r\n const buttonDisabled = value.length === 0;\r\n const message = \"Please enter your name\";\r\n const error = errorText!=='';\r\n const helperText =error?errorText:null;\r\n const title = 'Identification';\r\n\r\n return (\r\n \r\n \r\n \r\n
\r\n \r\n \r\n {message} \r\n
\r\n \r\n \r\n \r\n
\r\n \r\n );\r\n }\r\n}\r\n\r\nPublicExamPage.propTypes = {\r\n onComplete: PropTypes.func.isRequired,\r\n};\r\n\r\nconst mapStoreToProps = (store) => {\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n const examData=getExamData(store);\r\n\r\n return {\r\n isDownloadablePracticeExam: getIsDownloadablePracticeExam(clientSettingsData),\r\n formRunGuid: getFormRunGuid(examData),\r\n };\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tsetPublicExamUserName: (name) => dispatch(actions.setPublicExamUserName(name)),\r\n});\r\n\r\n\r\nPublicExamPage = withStyles(styles)(PublicExamPage);\r\nPublicExamPage = connect(mapStoreToProps, mapDispatchToProps)(PublicExamPage);\r\n\r\nexport { PublicExamPage };\r\n","// npm\r\nimport React from \"react\";\r\n\r\n// react\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { ContentBarTitle } from \"components/pages/exam/_layout/content/bar/title\";\r\n\r\n\r\nlet PageTitle = ({title}) => {\r\n return {title} ;\r\n};\r\n\r\nPageTitle = withMessages(PageTitle);\r\n\r\nexport { PageTitle };\r\n","import { check } from \"@xams-utils/check-types\";\r\n\r\nconst isAudio = (_url) => {\r\n const url=check.object(_url)?_url.url:_url;\r\n\r\n if (check.nonEmptyString(url)){\r\n const formats=[\"mp3\",\"m4a\"];\r\n\r\n return formats.some(format=>url.indexOf(format) !== -1);\r\n }\r\n return false;\r\n \r\n // return (check.nonEmptyString(url))?url.indexOf(\".mp3\") !== -1:false;\r\n\r\n};\r\n\r\nconst getAudioProps=(audioObj)=>{\r\n const props={}\r\n \r\n for (const key in audioObj) {\r\n if (key!=='elapsed'){\r\n props[key]=audioObj[key];\r\n }\r\n }\r\n\r\n return props;\r\n}\r\n\r\nconst getMediaPropsFromSection=(section)=>{\r\n const props={}\r\n \r\n if (check.nonEmptyObject(section)){\r\n const {data}=section;\r\n if (check.nonEmptyObject(data)){\r\n const {media}=data;\r\n if (check.nonEmptyObject(media)){ \r\n if (isAudio(media)){\r\n props.audio={...media}\r\n }\r\n }\r\n }\r\n }\r\n\r\n return props;\r\n}\r\n\r\nconst urlHasChanged=(audio, prevAudio)=>{\r\n if (audio && prevAudio){\r\n return audio.url!==prevAudio.url;\r\n }\r\n return audio || prevAudio;\r\n}\r\n\r\nconst hasAudioStateChanged=(audio, prevAudio)=>{\r\n if (audio && prevAudio){\r\n return audio.state!==prevAudio.state;\r\n }\r\n return audio || prevAudio;\r\n}\r\n\r\nexport {isAudio,getAudioProps, getMediaPropsFromSection, urlHasChanged,hasAudioStateChanged}","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getApiEndpointUrl } from \"redux/reducers/settings/app/selectors\";\r\nimport { getSessionData } from \"redux/reducers/selectors\";\r\nimport { getClientSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getOrgId } from \"redux/reducers/session/client/selectors\";\r\n\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport MusicNoteIcon from \"@material-ui/icons/MusicNote\";\r\nimport HourglassEmptyIcon from \"@material-ui/icons/HourglassEmpty\";\r\nimport PlayCircleFilledWhiteIcon from \"@material-ui/icons/PlayCircleFilledWhite\";\r\nimport PauseCircleFilledIcon from \"@material-ui/icons/PauseCircleFilled\";\r\nimport StopRoundedIcon from \"@material-ui/icons/StopRounded\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport Chip from \"@material-ui/core/Chip\";\r\n\r\nimport { urlHasChanged, hasAudioStateChanged } from \"./audio-helper\";\r\n\r\nconst CONSTANTS = { loading: 0, playing: 1, paused: 2, finished: 3, error: 4 };\r\n\r\nconst toHHMMSS = (secs) => {\r\n var sec_num = parseInt(secs, 10);\r\n var hours = Math.floor(sec_num / 3600);\r\n var minutes = Math.floor(sec_num / 60) % 60;\r\n var seconds = sec_num % 60;\r\n\r\n return [hours, minutes, seconds]\r\n .map((v) => (v < 10 ? \"0\" + v : v))\r\n .filter((v, i) => v !== \"00\" || i > 0)\r\n .join(\":\");\r\n};\r\n\r\nclass AudioChip extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.state = {\r\n audioState: props.state ? props.state : CONSTANTS.loading,\r\n elapsed: 0,\r\n duration: null,\r\n url: null,\r\n };\r\n\r\n this.canPlay = this.canPlay.bind(this);\r\n this.finishPlay = this.finishPlay.bind(this);\r\n this.audioDataLoaded = this.audioDataLoaded.bind(this);\r\n this.updateElapsed = this.updateElapsed.bind(this);\r\n this.handleError = this.handleError.bind(this);\r\n this.handleClick = this.handleClick.bind(this);\r\n this.playAudio = this.playAudio.bind(this);\r\n }\r\n\r\n getUrl(_url) {\r\n // const url =\r\n // \"~/ROOT/OCN West Midlands/Media/sample files/1. C2 Paper 1 - Questions.mp3\";\r\n\r\n if (_url.substring(0, 6) === \"~/ROOT\") {\r\n const { apiEndpointUrl, orgId } = this.props;\r\n let url = `${apiEndpointUrl}resource/ROOT/${_url.substring(7)}`;\r\n\r\n if (_url.indexOf(\"?orgID\") === -1) {\r\n url += \"?orgID=\" + orgId;\r\n }\r\n\r\n return url;\r\n }\r\n return _url;\r\n }\r\n\r\n startAudio(update = false) {\r\n const { url, elapsed } = this.props;\r\n const _url = this.getUrl(url);\r\n // const _url =\r\n // \"https://scorm-common.xams.co.uk/content/health_and_safety/audio/unit_1/M1_U1.1.000.mp3\";\r\n\r\n this.setState({ url: _url });\r\n\r\n this.startAudioTime = new Date();\r\n this.audio = new Audio(_url);\r\n\r\n if (elapsed > 0) {\r\n this.audio.currentTime = elapsed;\r\n }\r\n\r\n this.audio.addEventListener(\"canplaythrough\", this.canPlay);\r\n this.audio.addEventListener(\"ended\", this.finishPlay);\r\n this.audio.addEventListener(\"loadeddata\", this.audioDataLoaded);\r\n this.audio.addEventListener(\"timeupdate\", this.updateElapsed);\r\n this.audio.addEventListener(\"error\", this.handleError);\r\n }\r\n\r\n endAudio() {\r\n if (this.audio) {\r\n this.audio.pause();\r\n }\r\n clearTimeout(this.startTimer);\r\n this.audio.removeEventListener(\"canplay\", this.canPlay);\r\n this.audio.removeEventListener(\"ended\", this.finishPlay);\r\n this.audio.removeEventListener(\"loadeddata\", this.audioDataLoaded);\r\n this.audio.removeEventListener(\"timeupdate\", this.updateElapsed);\r\n this.audio.removeEventListener(\"error\", this.handleError);\r\n }\r\n\r\n componentDidMount() {\r\n this.startAudio();\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (urlHasChanged(this.props, prevProps)) {\r\n const audioState = this.props.state\r\n ? this.props.state\r\n : CONSTANTS.loading;\r\n\r\n this.setState({ audioState }, () => {\r\n this.endAudio();\r\n this.startAudio(true);\r\n });\r\n } else if (hasAudioStateChanged(this.props, prevProps)) {\r\n const { state: newAudioState } = this.props;\r\n\r\n if (newAudioState === CONSTANTS.paused) {\r\n this.audio.pause();\r\n } else if (newAudioState === CONSTANTS.playing) {\r\n this.audio.play();\r\n }\r\n }\r\n }\r\n\r\n componentWillUnmount() {\r\n this.endAudio();\r\n }\r\n\r\n canPlay() {\r\n const { audioState, duration } = this.state;\r\n const { autoPlay, autoDelay } = this.props;\r\n\r\n if (audioState === CONSTANTS.finished) {\r\n this.setState({ elapsed: duration });\r\n } else {\r\n if (autoPlay) {\r\n if (autoDelay) {\r\n const currentTime = new Date();\r\n const delayTime = autoDelay * 1000;\r\n const elapsed = currentTime - this.startAudioTime;\r\n console.log(\"autoDelay\", delayTime, elapsed);\r\n\r\n if (elapsed < delayTime) {\r\n this.startTimer = setTimeout(\r\n this.playAudio,\r\n delayTime - elapsed\r\n );\r\n return;\r\n }\r\n }\r\n this.playAudio();\r\n } else {\r\n this.setState({ audioState: CONSTANTS.paused });\r\n }\r\n }\r\n }\r\n\r\n playAudio() {\r\n this.elapsedTimer = new Date();\r\n this.audio.play();\r\n this.setState({ audioState: CONSTANTS.playing });\r\n }\r\n\r\n finishPlay() {\r\n const { autoRepeat, onFinished } = this.props;\r\n\r\n if (autoRepeat) {\r\n this.audio.play();\r\n this.setState({ audioState: CONSTANTS.playing });\r\n } else {\r\n this.setState({ audioState: CONSTANTS.finished });\r\n if (onFinished) {\r\n onFinished();\r\n }\r\n }\r\n }\r\n\r\n audioDataLoaded() {\r\n this.setState({ duration: Math.floor(this.audio.duration) });\r\n }\r\n\r\n updateElapsed() {\r\n const { duration } = this.state;\r\n const { elapsedUpdate, onElapsed } = this.props;\r\n\r\n if (duration) {\r\n console.log(this.audio.currentTime);\r\n const audioTime = Math.floor(this.audio.currentTime);\r\n this.setState({ elapsed: audioTime });\r\n\r\n if (elapsedUpdate) {\r\n const currentTime = new Date();\r\n const elapsedTime = currentTime - this.elapsedTimer;\r\n\r\n if (elapsedTime > elapsedUpdate * 1000) {\r\n if (onElapsed) {\r\n onElapsed(audioTime);\r\n this.elapsedTimer = currentTime;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n handleError(e) {\r\n console.log(e);\r\n this.setState({ audioState: CONSTANTS.error, error: e });\r\n }\r\n\r\n displayState() {\r\n const { audioState, url, error, elapsed, duration } = this.state;\r\n const { classes, autoPlay, canPause } = this.props;\r\n\r\n let text = \"\";\r\n if (audioState === CONSTANTS.loading) text = \"Loading\";\r\n else if (audioState === CONSTANTS.playing) text = \"Playing\";\r\n else if (audioState === CONSTANTS.paused) text = \"Paused\";\r\n else if (audioState === CONSTANTS.finished) text = \"Finished\";\r\n else if (audioState === CONSTANTS.error) text = `Error: ${url}`;\r\n\r\n const timing = !duration\r\n ? \"\"\r\n : !elapsed\r\n ? `${toHHMMSS(0)} / ${toHHMMSS(duration)}`\r\n : `${toHHMMSS(elapsed)} / ${toHHMMSS(duration)}`;\r\n\r\n const label = (\r\n \r\n
{text}
\r\n
{timing}
\r\n
\r\n );\r\n\r\n return (\r\n \r\n {/* {text} */}\r\n \r\n \r\n \r\n }\r\n label={label}\r\n className={classes.chip}\r\n deleteIcon={this.getIcon()}\r\n onDelete={this.handleClick}\r\n onClick={this.handleClick}\r\n />\r\n
\r\n );\r\n }\r\n\r\n getIcon() {\r\n const { audioState } = this.state;\r\n const { classes, autoPlay, canPause } = this.props;\r\n\r\n if (audioState === CONSTANTS.finished) {\r\n return ;\r\n } else if (audioState === CONSTANTS.loading) {\r\n return ;\r\n }\r\n\r\n if (!autoPlay && !canPause) {\r\n if (audioState === CONSTANTS.paused) {\r\n return ;\r\n } else if (audioState !== CONSTANTS.playing) {\r\n return ;\r\n }\r\n }\r\n\r\n if (canPause) {\r\n if (audioState === CONSTANTS.paused) {\r\n return ;\r\n } else if (audioState === CONSTANTS.playing) {\r\n return ;\r\n }\r\n } else {\r\n return ;\r\n }\r\n }\r\n\r\n handleClick() {\r\n const { audioState } = this.state;\r\n const { canPause } = this.props;\r\n\r\n if (audioState === CONSTANTS.playing && canPause) {\r\n this.audio.pause();\r\n this.setState({ audioState: CONSTANTS.paused });\r\n } else if (audioState === CONSTANTS.paused) {\r\n this.audio.play();\r\n this.setState({ audioState: CONSTANTS.playing });\r\n }\r\n }\r\n\r\n displayDuration() {\r\n const { elapsed, duration } = this.state;\r\n const { classes } = this.props;\r\n\r\n if (!duration) return null;\r\n\r\n const text = !elapsed\r\n ? `${toHHMMSS(0)} / ${toHHMMSS(duration)}`\r\n : `${toHHMMSS(elapsed)} / ${toHHMMSS(duration)}`;\r\n\r\n return (\r\n \r\n {text} \r\n
\r\n );\r\n }\r\n\r\n render() {\r\n const { classes, url } = this.props;\r\n\r\n return (\r\n \r\n {this.displayState()}\r\n {/* {this.displayDuration()} */}\r\n
\r\n );\r\n }\r\n}\r\n\r\nconst styles = ({ spacing, palette }) => {\r\n return {\r\n root: {\r\n display: \"flex\",\r\n justifyContent: \"flex-start\",\r\n },\r\n state: {\r\n paddingRight: spacing.unit * 2,\r\n },\r\n chip: {\r\n // margin: spacing.unit,\r\n },\r\n label: {\r\n display: \"flex\",\r\n justifyContent: \"flex-start\",\r\n \"&>div:first-child\": {\r\n minWidth: \"80px\",\r\n },\r\n },\r\n avatar: {\r\n backgroundColor: palette.primary.main,\r\n color: palette.background.light,\r\n },\r\n icon: { color: palette.primary.main },\r\n };\r\n};\r\n\r\nAudioChip = withStyles(styles)(AudioChip);\r\n\r\nconst mapStoreToProps = (store) => {\r\n const settingsData = getSettingsData(store);\r\n const appSettingsData = getAppSettingsData(settingsData);\r\n const sessionData = getSessionData(store);\r\n const clientSessionData = getClientSessionData(sessionData);\r\n\r\n return {\r\n apiEndpointUrl: getApiEndpointUrl(appSettingsData),\r\n orgId: getOrgId(clientSessionData),\r\n };\r\n};\r\n\r\nAudioChip = connect(mapStoreToProps)(AudioChip);\r\n\r\nexport { AudioChip, CONSTANTS };\r\n","// npm\r\nimport React, { Component } from \"react\";\r\n\r\n// material-ui\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport FormControlLabel from \"@material-ui/core/FormControlLabel\";\r\nimport Checkbox from \"@material-ui/core/Checkbox\";\r\n\r\n// react\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport { AudioChip } from \"components/functional/audio/audio-chip\";\r\n\r\n// AudioCheckPanel (not connected to styles)\r\n// --------------------------------------------------------------------\r\n\r\nclass AudioCheckPanel extends Component {\r\n state = {};\r\n render() {\r\n const { onChange, checked, classes, audio,message,label } = this.props;\r\n\r\n return (\r\n \r\n {message}
\r\n \r\n \r\n \r\n }\r\n label={label}\r\n />\r\n
\r\n \r\n );\r\n }\r\n}\r\n\r\nconst styles = ({ palette, spacing }) => ({\r\n panel: {\r\n flexGrow: 1,\r\n maxWidth: 800,\r\n boxSizing: \"border-box\",\r\n padding: spacing.unit*2,\r\n margin: spacing.unit*2,\r\n backgroundColor: palette.background.light,\r\n \"&>div:first-child\":{\r\n marginBottom: spacing.unit*2\r\n },\r\n \"&>div:last-child\":{\r\n marginTop: spacing.unit,\r\n marginLeft: spacing.unit\r\n } \r\n },\r\n text: {\r\n color: palette.background.contrastText,\r\n },\r\n});\r\n\r\nAudioCheckPanel = withStyles(styles)(AudioCheckPanel);\r\n\r\n// Export\r\n// --------------------------------------------------------------------\r\nexport { AudioCheckPanel };\r\n","import { check } from \"@xams-utils/check-types\";\r\n// redux (selectors)\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getContentData } from \"redux/reducers/exam/selectors\";\r\nimport {\r\n getCurrentQuestionId,\r\n getQuestionIdSetFromQuestion,\r\n} from \"redux/reducers/exam/content/selectors\";\r\n\r\nimport { getPaperPartId } from \"redux/reducers/exam/content/complex-selectors\";\r\nimport {\r\n getSectionsData,\r\n getQuestionsData,\r\n getMappingsData,\r\n} from \"redux/reducers/exam/content/selectors\";\r\n\r\nimport { isAudio } from \"components/functional/audio/audio-helper\";\r\n\r\nconst findCurrentSection = (currentQuestionId, mappings, sections) => {\r\n for (let key in mappings) {\r\n if (mappings[key].indexOf(currentQuestionId) !== -1) {\r\n const data = sections[key];\r\n //data.displayAllQuestions = key === \"817\";\r\n\r\n return { id: key, questions: mappings[key], data };\r\n }\r\n }\r\n\r\n return null;\r\n};\r\n\r\nconst shouldInclude = (currentPaperPartId, getPaperPartId) => (id) => {\r\n if (currentPaperPartId === id) {\r\n return true;\r\n }\r\n return getPaperPartId(id) === currentPaperPartId;\r\n};\r\n\r\nconst getMappingsAndSections = (store, _currentQuestionId = null) => {\r\n const contentData = getContentData(getExamData(store));\r\n const currentQuestionId = _currentQuestionId\r\n ? _currentQuestionId\r\n : getCurrentQuestionId(contentData);\r\n const questionIdSetFromQuestion = getQuestionIdSetFromQuestion(contentData);\r\n const _getPaperPartId = getPaperPartId(contentData);\r\n const currentPaperPartId = _getPaperPartId(currentQuestionId);\r\n\r\n let sectionsData = getSectionsData(contentData);\r\n let questionsData = getQuestionsData(contentData);\r\n let mappingsData = getMappingsData(contentData);\r\n\r\n if (currentPaperPartId) {\r\n // filter for nodes that only exist in the current paper part\r\n const cachedResults = {};\r\n const _shouldInclude = shouldInclude(\r\n currentPaperPartId,\r\n _getPaperPartId\r\n );\r\n\r\n // 1. Filter unneeded sections\r\n sectionsData = sectionsData.filter((_, sectionId) => {\r\n cachedResults[sectionId] = _shouldInclude(sectionId);\r\n return cachedResults[sectionId];\r\n });\r\n\r\n // 2. Filter unneeded questions\r\n questionsData = questionsData.filter((_, questionId) => {\r\n cachedResults[questionId] = _shouldInclude(questionId);\r\n return cachedResults[questionId];\r\n });\r\n\r\n // 3. Filter unneeded mappings\r\n mappingsData = mappingsData.filter((_, mappingId) => {\r\n return /*mappingId === 'root' || */ cachedResults[mappingId];\r\n });\r\n\r\n // 4. Convert paper part mapping to be 'root'\r\n const paperPartMapping = mappingsData.get(currentPaperPartId);\r\n mappingsData = mappingsData.delete(currentPaperPartId);\r\n mappingsData = mappingsData.set(\"root\", paperPartMapping);\r\n }\r\n\r\n // sectionsData = calculateRootSectionsMarks(sectionsData, mappingsData);\r\n\r\n const mappings = mappingsData.toJS();\r\n const sections = sectionsData.toJS();\r\n\r\n return { mappings, sections, questionIdSetFromQuestion, currentQuestionId };\r\n};\r\n\r\nconst checkSectionsForAudio = (sections) => {\r\n let hasAudio = false;\r\n\r\n for (let key in sections) {\r\n const { media } = sections[key];\r\n if (isAudio(media)) {\r\n hasAudio = true;\r\n }\r\n\r\n if (hasAudio) {\r\n break;\r\n }\r\n }\r\n\r\n return hasAudio;\r\n};\r\n\r\nconst displayAllQuestionsInSection = (section) =>\r\n section && section.data && section.data.displayAllQuestions;\r\n\r\n\r\nexport { getMappingsAndSections, findCurrentSection, checkSectionsForAudio, displayAllQuestionsInSection };\r\n","import { check } from \"@xams-utils/check-types\";\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nconst trimIt = (text) => {\r\n return check.string(text) ? text.trim() : text;\r\n};\r\n\r\nconst getSettings = (messages) => {\r\n const messageId = MESSAGE_IDS.AUDIO.TEST_PAGE;\r\n const message = messages[messageId];\r\n\r\n const defaultSettings = {\r\n title: \"Audio Check Page\",\r\n text: `This exam contains some audio. Click the audio chip below to play some music. If you can hear the music: tick the check box and then select the START button. `,\r\n autoPlay: \"false\",\r\n autoDelay: 1,\r\n autoRepeat: \"true\",\r\n canPause: \"false\",\r\n check: \"Yes I can hear the music\",\r\n url: \"https://scorm-common.xams.co.uk/content/sample/no-worries-16479.mp3\",\r\n };\r\n\r\n const a = check.nonEmptyString(message)\r\n ? message.split(\",\").map(trimIt)\r\n : {};\r\n const settings = a.reduce((obj, setting) => {\r\n const items = setting.split(\":\").map(trimIt);\r\n obj[items[0]] = items.filter((item, index) => index > 0).join(\":\");\r\n return obj;\r\n }, defaultSettings);\r\n\r\n const bools = [\"autoPlay\", \"canPause\", \"autoRepeat\"];\r\n bools.forEach(\r\n (bool) => (settings[bool] = settings[bool].toLowerCase() === \"true\")\r\n );\r\n\r\n return settings;\r\n};\r\n\r\nexport { getSettings };\r\n","// npm\r\nimport React, { Component } from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n\r\n// react (layout)\r\nimport { ExamLayout } from \"../../../../exam/_layout/layout\";\r\nimport { Align } from \"components/layout/align\";\r\n\r\n// react (concrete)\r\nimport { PageTitle } from \"./title\";\r\nimport { StartExamButton } from \"../page/start-exam-button\";\r\nimport { AudioCheckPanel } from \"./audio-check-panel\";\r\nimport { ReturnToSchedulesButton } from \"../page/return-to-schedules-button\";\r\n\r\n// redux (selectors)\r\nimport { getGuid } from \"redux/reducers/exam/selectors\";\r\nimport { getExamData, getSchedulesData } from \"redux/reducers/selectors\";\r\nimport { getVersion } from \"redux/reducers/schedules/schedule/selectors\";\r\nimport { getScheduleDataByExamGuid } from \"redux/reducers/schedules/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport {\r\n getProctoringMode,\r\n getPreviewMode,\r\n} from \"redux/reducers/settings/client/selectors\";\r\n\r\nimport {\r\n getMappingsAndSections,\r\n checkSectionsForAudio,\r\n} from \"components/pages/exam/_layout/content/panel/content-helper\";\r\n\r\nimport {getSettings} from \"./settings\"\r\n\r\n// AudioCheckPage (not connected to store)\r\n// -----------------------------------------------------------------\r\n\r\nclass AudioCheckPage extends Component {\r\n state = {};\r\n constructor(props) {\r\n super(props);\r\n\r\n this.settings=getSettings(props.messages);\r\n\r\n this.handleAudioChecked = this.handleAudioChecked.bind(this);\r\n this.audioChecked = this.audioChecked.bind(this);\r\n\r\n this.state = { audioChecked: false };\r\n }\r\n\r\n componentDidMount() {\r\n const { hasAudio } = this.props;\r\n\r\n if (!hasAudio) {\r\n this.audioChecked();\r\n } else {\r\n const actions = [\r\n \"play\",\r\n \"pause\",\r\n \"stop\",\r\n \"seekbackward\",\r\n \"seekforward\", \r\n \"previoustrack\",\r\n \"nexttrack\",\r\n \"skipad\",\r\n ];\r\n actions.forEach((action) => {\r\n try {\r\n navigator.mediaSession.setActionHandler(\r\n action,\r\n function () {}\r\n );\r\n } catch (e) {}\r\n });\r\n }\r\n }\r\n\r\n handleAudioChecked() {\r\n const { audioChecked } = this.state;\r\n\r\n this.setState({ audioChecked: !audioChecked });\r\n }\r\n\r\n audioChecked() {\r\n const { onAudioChecked } = this.props;\r\n\r\n onAudioChecked();\r\n }\r\n\r\n render() {\r\n const { hasAudio } = this.props;\r\n if (!hasAudio) {\r\n return null;\r\n }\r\n\r\n const { audioChecked } = this.state;\r\n const { showReturnButton, previewMode } = this.props;\r\n\r\n const autoPlay = previewMode?false:this.settings.autoPlay;\r\n\r\n const audio = {\r\n url: this.settings.url,\r\n autoPlay,\r\n autoDelay: this.settings.autoDelay,\r\n canPause: this.settings.canPause,\r\n autoRepeat: this.settings.autoRepeat,\r\n };\r\n\r\n const message = this.settings.text;\r\n\r\n const audioProps = {\r\n checked: audioChecked,\r\n onChange: this.handleAudioChecked,\r\n audio,\r\n message,\r\n label: this.settings.check,\r\n };\r\n const buttonProps = {\r\n onClick: this.audioChecked,\r\n disabled: !audioChecked,\r\n };\r\n const titleProps = { title: this.settings.title };\r\n\r\n const examLayoutProps = {\r\n bar: {\r\n content: (\r\n \r\n \r\n {showReturnButton ? : null}\r\n \r\n \r\n ),\r\n },\r\n centre: {\r\n content: (\r\n \r\n \r\n \r\n ),\r\n },\r\n };\r\n\r\n return ;\r\n }\r\n}\r\n\r\nAudioCheckPage.propTypes = {\r\n onExamStart: PropTypes.func.isRequired,\r\n showReturnButton: PropTypes.bool.isRequired,\r\n};\r\n\r\n// AudioCheckPage (connected to store)\r\n// -----------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const examGuid = getGuid(examData);\r\n\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n const schedulesData = getSchedulesData(store);\r\n const scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n const proctoringMode = getProctoringMode(clientSettingsData);\r\n const showReturnButton =\r\n proctoringMode == 1 ? false : !getVersion(scheduleData);\r\n\r\n const { sections, mappings } = getMappingsAndSections(store);\r\n const hasAudio = checkSectionsForAudio(sections, mappings);\r\n // const hasAudio = false;\r\n\r\n const previewMode = getPreviewMode(clientSettingsData);\r\n\r\n return {\r\n previewMode,\r\n showReturnButton,\r\n hasAudio,\r\n };\r\n};\r\n\r\nAudioCheckPage = connect(mapStoreToProps)(AudioCheckPage);\r\n\r\nAudioCheckPage = withMessages(AudioCheckPage);\r\n\r\n// AudioCheckPage (EXPORT)\r\n// -----------------------------------------------------------------\r\nexport { AudioCheckPage };\r\n","\r\nimport {Machine} from 'xstate'\r\nimport {XStateConfig} from './xstate-config'\r\n\r\n\r\n// #########################################\r\n// STATE NAMES\r\n// #########################################\r\n\r\nconst STATES = {\r\n\tATTEMPTING_TO_RESUME: 'attempting_to_resume',\r\n\tAUDIO_CHECK_SCREEN: 'audio_check_screen',\r\n\tINTRO_SCREEN: 'intro_screen',\r\n\tPUBLIC_EXAM: 'public_exam',\r\n\tSTARTING_ACTIVITY_LOGGER: 'starting_activity_logger',\r\n\tRESUMING_ACTIVITY_LOGGER: 'resuming_activity_logger',\t\r\n\tREDIRECT_TO_EXAM_QUESTIONS: 'redirecting_to_exam_questions'\r\n}\r\n\r\n\r\n// #########################################\r\n// EVENT NAMES\r\n// #########################################\r\n\r\nconst EVENTS = {\r\n\tCAN_RESUME_EXAM: 'can_resume_exam',\r\n\tCANT_RESUME_EXAM: 'cant_resume_exam',\r\n\tSTARTED_ACTIVITY_LOGGER: 'started_activity_logger',\r\n\tRESUMED_ACTIVITY_LOGGER: 'resumed_activity_logger',\r\n\tDISPLAY_AUDIO_CHECK: 'display_audio_check',\t\r\n\tDISPLAY_INTRO: 'display_intro',\r\n\tSTART_EXAM: 'start_exam'\r\n}\r\n\r\n\r\n// #########################################\r\n// STATES\r\n// #########################################\r\n\r\nconst attemptingToResume = new XStateConfig();\r\nattemptingToResume.addTransition(EVENTS.CANT_RESUME_EXAM, STATES.PUBLIC_EXAM);\r\nattemptingToResume.addTransition(EVENTS.CAN_RESUME_EXAM, STATES.RESUMING_ACTIVITY_LOGGER);\r\n\r\nconst publicExam = new XStateConfig();\r\npublicExam.addTransition(EVENTS.DISPLAY_INTRO, STATES.AUDIO_CHECK_SCREEN);\r\n\r\nconst audioCheckScreen = new XStateConfig();\r\naudioCheckScreen.addTransition(EVENTS.START_EXAM, STATES.INTRO_SCREEN);\r\n\r\nconst introScreen = new XStateConfig();\r\nintroScreen.addTransition(EVENTS.DISPLAY_AUDIO_CHECK, STATES.STARTING_ACTIVITY_LOGGER);\r\n\r\nconst resumingActivityLogger = new XStateConfig();\r\nresumingActivityLogger.addTransition(EVENTS.RESUMED_ACTIVITY_LOGGER, STATES.REDIRECT_TO_EXAM_QUESTIONS);\r\n\r\nconst startingActivityLogger = new XStateConfig();\r\nstartingActivityLogger.addTransition(EVENTS.STARTED_ACTIVITY_LOGGER, STATES.REDIRECT_TO_EXAM_QUESTIONS);\r\n\r\nconst redirectToExamQuestions = new XStateConfig();\r\n\r\n\r\n// #########################################\r\n// MACHINE\r\n// #########################################\r\n\r\nconst _examIntro = new XStateConfig();\r\n_examIntro.initialState = STATES.ATTEMPTING_TO_RESUME;\r\n_examIntro.addState(STATES.ATTEMPTING_TO_RESUME, attemptingToResume);\r\n_examIntro.addState(STATES.PUBLIC_EXAM, publicExam);\r\n_examIntro.addState(STATES.INTRO_SCREEN, introScreen);\r\n_examIntro.addState(STATES.AUDIO_CHECK_SCREEN, audioCheckScreen);\r\n_examIntro.addState(STATES.RESUMING_ACTIVITY_LOGGER, resumingActivityLogger);\r\n_examIntro.addState(STATES.STARTING_ACTIVITY_LOGGER, startingActivityLogger);\r\n_examIntro.addState(STATES.REDIRECT_TO_EXAM_QUESTIONS, redirectToExamQuestions);\r\n\r\nconst machine = Machine(_examIntro.toObject());\r\nmachine.id = \"Exam Intro Machine\";\r\n\r\n\r\n// #########################################\r\n// EXPORT\r\n// #########################################\r\n\r\nconst examIntro = {\r\n\tmachine,\r\n\tEVENTS: {...EVENTS},\r\n\tSTATES: {...STATES}\r\n}\r\n\r\nexport {examIntro}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// xams-components\r\nimport {StateMachine, StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {ExamIntroPage} from './page'\r\nimport {CommencedChecker} from './commenced-checker'\r\nimport {RedirectToExamQuestions} from './redirect-to-questions'\r\nimport {StartActivityLogger} from './start-activity-logger'\r\nimport {PublicExamPage} from './public-exam'\r\nimport { AudioCheckPage } from './audio_check/audio-check-page'\r\n\r\n// machines\r\nimport {examIntro} from 'machines/exam-intro'\r\n\r\n// constants\r\nimport { ACTIVITIES } from \"libs/activity_logger/activity-logger\";\r\n\r\n\r\nconst {EVENTS, STATES} = examIntro;\r\n\r\n\r\nconst ExamIntroPageMachine = ({examGuid}) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{{\r\n\t\t\t\t\t[STATES.ATTEMPTING_TO_RESUME]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.PUBLIC_EXAM]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{({onComplete}) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\t\r\n\t\t\t\t\t[STATES.AUDIO_CHECK_SCREEN]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{({onAudioChecked}) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t[STATES.INTRO_SCREEN]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{({onExamStart}) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.STARTING_ACTIVITY_LOGGER]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\t\t\t\t\t\r\n\t\t\t\t\t[STATES.RESUMING_ACTIVITY_LOGGER]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t[STATES.REDIRECT_TO_EXAM_QUESTIONS]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t)\r\n\t\t\t\t}}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\nExamIntroPageMachine.propTypes = {\r\n\texamGuid: PropTypes.string.isRequired\r\n}\r\n\r\n\r\nexport {ExamIntroPageMachine}","// npm\r\nimport React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\nimport PropTypes from \"prop-types\";\r\n\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getShowExamInFullScreen } from \"redux/reducers/exam/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getOrgFullScreenExam } from \"redux/reducers/settings/client/selectors\";\r\n\r\nimport { SET_SHOW_EXAM_IN_FULL_SCREEN } from \"redux/reducers/exam/action-types\";\r\n\r\n// react\r\nimport { ExamIntroPageMachine } from \"./machine\";\r\n\r\nclass ExamIntro extends Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n const {\r\n match: { params },\r\n } = props;\r\n\r\n const { needToSetShowExamInFullScreen, setShowExamInFullScreen } =\r\n props;\r\n\r\n if (needToSetShowExamInFullScreen) {\r\n setShowExamInFullScreen(true);\r\n }\r\n\r\n this.examGuid = params.examGuid;\r\n }\r\n\r\n render() {\r\n return ;\r\n }\r\n}\r\n\r\nExamIntro.propTypes = {\r\n match: PropTypes.shape({\r\n params: PropTypes.shape({\r\n examGuid: PropTypes.string.isRequired,\r\n }).isRequired,\r\n }).isRequired,\r\n};\r\n\r\nconst mapStoreToProps = (store) => {\r\n let needToSetShowExamInFullScreen = false;\r\n\r\n const examData = getExamData(store);\r\n const showExamInFullScreen = getShowExamInFullScreen(examData);\r\n\r\n if (!showExamInFullScreen) {\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n needToSetShowExamInFullScreen =\r\n getOrgFullScreenExam(clientSettingsData);\r\n }\r\n\r\n return { needToSetShowExamInFullScreen };\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setShowExamInFullScreen: (value) => {\r\n dispatch({\r\n type: SET_SHOW_EXAM_IN_FULL_SCREEN,\r\n value,\r\n });\r\n },\r\n});\r\n\r\nExamIntro = connect(mapStoreToProps, mapDispatchToProps)(ExamIntro);\r\n\r\nexport { ExamIntro };\r\n","\r\n// npm\r\nimport React from 'react'\r\n\r\n// react (layout)\r\nimport {ContentBarTitle} from 'components/pages/exam/_layout/content/bar/title'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nlet OutroTitle = ({messages, title}) => {\r\n\tconst message = messages[MESSAGE_IDS.EXAM.ASSESSMENT_COMPLETE];\r\n\tconst barTitle=title?`${message} - ${title}`:message;\r\n\r\n\treturn (\r\n\t\r\n\t\t{barTitle}\r\n\t \r\n)}\r\n\r\n\r\nOutroTitle = withMessages(OutroTitle);\r\n\r\n\r\nexport {OutroTitle}","\r\n// npm\r\nimport React from 'react'\r\nimport {withRouter} from 'react-router-dom'\r\n\r\n// material-ui\r\nimport ExitToAppIcon from '@material-ui/icons/ExitToApp'\r\n\r\n// react\r\nimport {ActionButton} from 'components/layout/action_button/button'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\n// CloseButton (not connected to router)\r\n// --------------------------------------------------\r\nlet CloseButton = ({history, messages, proctorio}) =>\r\n{\r\n\tconst onClick = proctorio? () => history.push('/proctorio/finish') : () => history.push('/schedules');\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{messages[MESSAGE_IDS.GENERAL.CLOSE]}\r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\n// CloseButton (connected to router)\r\n// --------------------------------------------------\r\nCloseButton = withRouter(CloseButton);\r\nCloseButton = withMessages(CloseButton);\r\n\r\n\r\n// Export\r\n// --------------------------------------------------\r\nexport {CloseButton}","// npm\r\nimport React from \"react\";\r\nimport { connect } from \"react-redux\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\n// material-ui\r\nimport Card from \"@material-ui/core/Card\";\r\nimport CardContent from \"@material-ui/core/CardContent\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\n// react\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\n// redux (selectors)\r\nimport { getMessageData } from \"redux/reducers/exam/selectors\";\r\nimport { getOutroText, getProctoredOutroText } from \"redux/reducers/exam/messages/selectors\";\r\n\r\nimport {\r\n getExamData,\r\n getSettingsData,\r\n getSchedulesData,\r\n} from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport {\r\n getProctoringMode,\r\n getProctoringType,\r\n} from \"redux/reducers/settings/client/selectors\";\r\nimport { isPracticeExam } from \"redux/reducers/exam/selectors\";\r\nimport { getScheduleDataByExamGuid } from \"redux/reducers/schedules/selectors\";\r\nimport { getGuid } from \"redux/reducers/exam/selectors\";\r\nimport { getProctorProvider } from \"redux/reducers/schedules/schedule/selectors\";\r\n\r\n// messages\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nlet OutroTextPanel = (props) => {\r\n const {\r\n text,\r\n proctoredText,\r\n classes,\r\n messages,\r\n proctoringMode,\r\n practiceExam,\r\n proctoringProvider,\r\n } = props;\r\n\r\n return (\r\n \r\n \r\n \r\n {getText(\r\n text,\r\n proctoredText,\r\n proctoringMode,\r\n messages,\r\n practiceExam,\r\n proctoringProvider\r\n )}\r\n \r\n \r\n \r\n );\r\n};\r\n\r\nconst getText = (\r\n text,\r\n proctoredText,\r\n proctoringMode,\r\n messages,\r\n practiceExam,\r\n proctoringProvider\r\n) => {\r\n if (proctoringMode === \"1\" || proctoringMode === \"2\") {\r\n if (check.nonEmptyString(proctoredText)){\r\n return proctoredText;\r\n }\r\n\r\n const mode = practiceExam\r\n ? \"PRACTICE\"\r\n : proctoringMode === \"1\"\r\n ? \"SINGLE\"\r\n : \"MULTI\";\r\n\r\n const proctoringName=proctoringProvider?proctoringProvider.replaceAll(' ','_')+'_':\"\";\r\n const messageId =\r\n `PROCTORING_${proctoringName}EXAM_OUTRO_${mode}`.toUpperCase();\r\n\r\n console.log(\"proctoringMode\", proctoringMode);\r\n console.log(\"proctoringProvider\", proctoringProvider);\r\n console.log(\"proctoringName\", proctoringName);\r\n console.log(\"practiceExam\", practiceExam);\r\n console.log(\"mode\", mode);\r\n console.log(\"messageId\", messageId);\r\n\r\n if (\r\n MESSAGE_IDS.EXAM[messageId] &&\r\n messages[MESSAGE_IDS.EXAM[messageId]]\r\n ) {\r\n console.log(\r\n \"Return\",\r\n MESSAGE_IDS.EXAM[messageId],\r\n messages[MESSAGE_IDS.EXAM[messageId]]\r\n );\r\n return messages[MESSAGE_IDS.EXAM[messageId]];\r\n }\r\n\r\n console.log(\"Using Default\");\r\n if (proctoringMode == \"1\") {\r\n console.log(\r\n \"Return\",\r\n MESSAGE_IDS.EXAM.PROCTORING_EXAM_OUTRO_SINGLE,\r\n messages[MESSAGE_IDS.EXAM.PROCTORING_EXAM_OUTRO_SINGLE]\r\n );\r\n return messages[MESSAGE_IDS.EXAM.PROCTORING_EXAM_OUTRO_SINGLE];\r\n } else if (proctoringMode == \"2\") {\r\n console.log(\r\n \"Return\",\r\n MESSAGE_IDS.EXAM.PROCTORING_EXAM_OUTRO_MULTI,\r\n messages[MESSAGE_IDS.EXAM.PROCTORING_EXAM_OUTRO_MULTI]\r\n );\r\n return messages[MESSAGE_IDS.EXAM.PROCTORING_EXAM_OUTRO_MULTI];\r\n }\r\n }\r\n\r\n console.log(\"Return\", text);\r\n\r\n return text;\r\n};\r\n\r\nconst mapStoreToProps = (store) => {\r\n const clientSettingsData = getClientSettingsData(getSettingsData(store));\r\n const examData = getExamData(store);\r\n const examGuid = getGuid(examData);\r\n const schedulesData = getSchedulesData(store);\r\n const scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n const messageData=getMessageData(examData);\r\n\r\n return {\r\n text: getOutroText(messageData),\r\n proctoredText: getProctoredOutroText(messageData),\r\n proctoringMode: getProctoringMode(clientSettingsData),\r\n practiceExam: isPracticeExam(examData),\r\n proctoringType: getProctoringType(clientSettingsData),\r\n proctoringProvider: getProctorProvider(scheduleData),\r\n };\r\n};\r\n\r\nOutroTextPanel = connect(mapStoreToProps)(OutroTextPanel);\r\n\r\nconst styles = ({ palette, spacing, breakpoints }) => ({\r\n text: {\r\n color: palette.background.contrastText,\r\n },\r\n panel: {\r\n backgroundColor: palette.background.light,\r\n //maxWidth: 800,\r\n [breakpoints.down(840)]: {\r\n width: \"calc(100% - 40px)\",\r\n marginLeft: 20,\r\n marginRight: 20,\r\n },\r\n [breakpoints.up(841)]: {\r\n width: 800,\r\n },\r\n marginTop: spacing.unit * 2,\r\n alignSelf: \"stretch\",\r\n },\r\n});\r\n\r\nOutroTextPanel = withMessages(withStyles(styles)(OutroTextPanel));\r\n\r\nexport { OutroTextPanel };\r\n","\r\n// redux (action-types)\r\nimport {SET_FORMRUN_ID, CLEAR_EXAM_DATA} from 'redux/reducers/exam/action-types'\r\nimport {SET_EXAM_RESULT} from 'redux/reducers/exam/result/action-types'\r\nimport {SET_COMPLETED} from 'redux/reducers/schedules/schedule/action-types'\r\n\r\n\r\nconst completeSchedule = (guid) => ({\r\n\ttype: SET_COMPLETED,\r\n\tguid\r\n});\r\n\r\nconst setExamResult = (resultData) => ({\r\n\ttype: SET_EXAM_RESULT,\r\n\t...resultData\r\n});\r\n\r\nconst setFormRunId = (id) => ({\r\n\ttype: SET_FORMRUN_ID,\r\n\tid\r\n});\r\n\r\nconst clearExamPaper = () => ({\r\n\ttype: CLEAR_EXAM_DATA\r\n});\r\n\r\n\r\nexport {completeSchedule, setExamResult, setFormRunId, clearExamPaper}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport {withRouter} from 'react-router-dom'\r\n\r\n// material-ui\r\nimport Button from '@material-ui/core/Button'\r\n\r\n// redux (actions)\r\nimport {clearExamPaper} from '../actions'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\n// Not connected\r\n// --------------------------------------------------------------------------\r\n\r\nlet ImmediateResitButton = ({history, match:{params:{examGuid}}, messages}) =>\r\n{\r\n\tconst onClick = () => {\r\n\t\tsetTimeout(() => history.replace(`/exam/${examGuid}`), 10);\r\n\t\thistory.push('/schedules');\r\n\t}\r\n\r\n\tconst buttonText = messages[MESSAGE_IDS.EXAM.SIT_NEXT_RESIT];\r\n\treturn {buttonText} ;\r\n}\r\n\r\nImmediateResitButton.propTypes = {\r\n\tclearExamPaper: PropTypes.func.isRequired,\r\n\thistory: PropTypes.object.isRequired,\r\n\tmessages: PropTypes.object.isRequired,\r\n\tmatch: PropTypes.shape({\r\n\t\tparams: PropTypes.shape({\r\n\t\t\texamGuid: PropTypes.string.isRequired\r\n\t\t}).isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\n// Connected to router\r\n// --------------------------------------------------------------------------\r\n\r\nImmediateResitButton = withRouter(ImmediateResitButton);\r\n\r\n\r\n// Conenected to store\r\n// --------------------------------------------------------------------------\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tclearExamPaper: () => dispatch(clearExamPaper())\r\n});\r\n\r\nImmediateResitButton = connect(undefined, mapDispatchToProps)(ImmediateResitButton);\r\nImmediateResitButton = withMessages(ImmediateResitButton);\r\n\r\n\r\n// Export\r\n// --------------------------------------------------------------------------\r\nexport {ImmediateResitButton}","// npm\r\nimport React, { Component } from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\n// material-ui\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\nimport List from \"@material-ui/core/List\";\r\nimport ListItem from \"@material-ui/core/ListItem\";\r\nimport ListItemText from \"@material-ui/core/ListItemText\";\r\nimport ListItemAvatar from \"@material-ui/core/ListItemAvatar\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport DoneIcon from \"@material-ui/icons/Done\";\r\nimport ClearIcon from \"@material-ui/icons/Clear\";\r\n\r\n// react\r\nimport { ImmediateResitButton } from \"./immediate-resit-button\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\n// redux (selectors)\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getResultData } from \"redux/reducers/exam/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getResultFeedbackMask } from \"redux/reducers/settings/client/selectors\";\r\n\r\n// LearnerFeedbackPanel (not conected to store)\r\n// --------------------------------------------------------------------------\r\n\r\nconst styles = ({ spacing, palette, breakpoints }) => ({\r\n paper: {\r\n marginTop: spacing.unit * 2,\r\n [breakpoints.down(840)]: {\r\n width: \"calc(100% - 40px)\",\r\n marginLeft: 20,\r\n marginRight: 20,\r\n },\r\n [breakpoints.up(841)]: {\r\n width: 800,\r\n },\r\n alignSelf: \"stretch\",\r\n },\r\n results: {\r\n padding: spacing.unit * 2,\r\n },\r\n avatar: {\r\n background: palette.primary.main,\r\n },\r\n listItem: {\r\n alignItems: \"flex-start\",\r\n },\r\n text: {\r\n color: palette.background.contrastText,\r\n fontWeight: 700,\r\n },\r\n});\r\n\r\nconst getTable = (rows) => {\r\n return (\r\n \r\n {rows.map((row) => (\r\n \r\n {row[0]} \r\n : {row[1]} \r\n \r\n ))}\r\n
\r\n );\r\n};\r\n\r\nconst getWhatToShowFromMask = (mask) => {\r\n let showMarks = false;\r\n let showGrade = false;\r\n let showPercentage = false;\r\n\r\n if (mask === -1) {\r\n showMarks = true;\r\n showPercentage = true;\r\n } else {\r\n const binary = getBinaryFromMask(mask, 3);\r\n\r\n showMarks = getBinaryValue(binary, 2);\r\n showGrade = getBinaryValue(binary, 1);\r\n showPercentage = getBinaryValue(binary, 0);\r\n }\r\n\r\n return { showMarks, showGrade, showPercentage };\r\n};\r\n\r\nconst getBinaryFromMask = (mask, bits) => {\r\n let binaryValue = mask.toString(2);\r\n let leading = \"\";\r\n\r\n const leadingLength = bits - binaryValue.length;\r\n\r\n if (leadingLength > 0) {\r\n leading = new Array(leadingLength + 1).join(\"0\");\r\n } else if (leadingLength < 0) {\r\n binaryValue = binaryValue.substr(binaryValue.length - bits);\r\n }\r\n\r\n return leading + binaryValue;\r\n};\r\n\r\nconst getBinaryValue = (binary, place) => {\r\n return check.nonEmptyString(binary) && binary.length > place\r\n ? binary.charAt(place) === \"1\"\r\n : false;\r\n};\r\n\r\nclass LearnerFeedbackPanel extends Component {\r\n state = {};\r\n\r\n getResultTable() {\r\n const { result, resultFeedbackMask } = this.props;\r\n const { marks, totalMarks, score, grade } = result;\r\n const { showMarks, showGrade, showPercentage } =\r\n getWhatToShowFromMask(resultFeedbackMask);\r\n\r\n const rows = [];\r\n\r\n if (showMarks) {\r\n rows.push([\"Your marks\", `${marks} / ${totalMarks}`]);\r\n }\r\n if (showGrade) {\r\n rows.push([\"Your grade\", grade]);\r\n }\r\n if (showPercentage) {\r\n rows.push([\"Your result\", `${score} %`]);\r\n }\r\n\r\n return rows.length > 0 ? getTable(rows) : null;\r\n }\r\n\r\n get Avatar() {\r\n const { result, classes } = this.props;\r\n const { passed } = result;\r\n if (!check.boolean(passed)) return null;\r\n\r\n const icon = passed ? : ;\r\n return (\r\n \r\n {icon} \r\n \r\n );\r\n }\r\n\r\n get ResultStillBeingProcessed() {\r\n const { classes, result } = this.props;\r\n const { message } = result;\r\n\r\n return (\r\n \r\n {message} \r\n
\r\n );\r\n }\r\n\r\n get Feedback() {\r\n const { result, classes } = this.props;\r\n const { message, allowImmediateResit, resultStillBeingProcessed } =\r\n result;\r\n\r\n if (resultStillBeingProcessed) {\r\n return this.ResultStillBeingProcessed;\r\n }\r\n\r\n const listItemClass =\r\n !check.nonEmptyString(message) || message.length > 80\r\n ? classes.listItem\r\n : null;\r\n\r\n return (\r\n \r\n \r\n \r\n {this.Avatar}\r\n \r\n \r\n
\r\n {this.displayResult()}\r\n {allowImmediateResit ? this.ResitButton : null}\r\n \r\n );\r\n }\r\n\r\n displayResult() {\r\n const { classes } = this.props;\r\n const results = this.getResultTable();\r\n\r\n return results ? (\r\n \r\n \r\n \r\n {results} \r\n
\r\n \r\n ) : null;\r\n }\r\n\r\n get ResitButton() {\r\n const { classes } = this.props;\r\n return (\r\n \r\n \r\n \r\n \r\n
\r\n \r\n );\r\n }\r\n\r\n render() {\r\n const { classes } = this.props;\r\n\r\n return {this.Feedback} ;\r\n }\r\n}\r\n\r\nLearnerFeedbackPanel.propTypes = {\r\n result: PropTypes.shape({\r\n marks: PropTypes.number.isRequired,\r\n message: PropTypes.string.isRequired,\r\n passed: PropTypes.bool.isRequired,\r\n score: PropTypes.number.isRequired,\r\n totalMarks: PropTypes.number.isRequired,\r\n allowImmediateResit: PropTypes.bool.isRequired,\r\n }),\r\n};\r\n\r\n// LearnerFeedbackPanel (conected to store)\r\n// --------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const resultData = getResultData(examData);\r\n\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n const resultFeedbackMask = getResultFeedbackMask(clientSettingsData);\r\n\r\n return { result: resultData.toJS(), resultFeedbackMask };\r\n};\r\n\r\nLearnerFeedbackPanel = connect(mapStoreToProps)(LearnerFeedbackPanel);\r\n\r\nLearnerFeedbackPanel = withStyles(styles)(LearnerFeedbackPanel);\r\n\r\n// Export\r\n// --------------------------------------------------------------------------\r\nexport { LearnerFeedbackPanel };\r\n","\r\n// npm\r\nimport React, { Component } from 'react';\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// react (layout)\r\nimport {ExamLayout} from 'components/pages/exam/_layout/layout'\r\nimport {Align} from 'components/layout/align'\r\n\r\n// react (concrete)\r\nimport {OutroTitle} from './outro-title'\r\nimport {CloseButton} from './close-button'\r\nimport {OutroTextPanel} from './outro-text-panel'\r\nimport {LearnerFeedbackPanel} from './learner-feedback-panel'\r\n\r\n// redux (selectors)\r\nimport {getExamData, getSchedulesData, getProctorioData} from 'redux/reducers/selectors'\r\nimport {getVersion} from 'redux/reducers/schedules/schedule/selectors'\r\nimport {getGuid, shouldShowFeedback} from 'redux/reducers/exam/selectors'\r\nimport {getScheduleDataByExamGuid} from 'redux/reducers/schedules/selectors'\r\n\r\nimport {getSettingsData} from 'redux/reducers/selectors'\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getProctoringMode} from 'redux/reducers/settings/client/selectors'\r\nimport { getParent } from \"redux/reducers/proctorio/selectors\";\r\n\r\n\r\n// ExamOutroPage (not connected to store)\r\n// -----------------------------------------------------------------\r\n\r\nlet ExamOutroPage = ({showCloseButton, showFeedbackPanel, proctorioActive}) =>\r\n{\r\n\r\n\tconst closeButtonProps = {\r\n\t\tproctorio: proctorioActive\r\n\t}\r\n\tconst examLayoutProps = {\r\n\t\tbar: {\r\n\t\t\tcontent: (\r\n\t\t\t\t\r\n\t\t\t\t\t \r\n\t\t\t\t\t{showCloseButton ? : null}\r\n\t\t\t\t \r\n\t\t\t)\r\n\t\t},\r\n\t\tcentre: {\r\n\t\t\tcontent: (\r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{showFeedbackPanel ? : null}\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t
\r\n\t\t\t\t \r\n\t\t\t)\r\n\t\t}\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nExamOutroPage.propTypes = {\r\n\tshowCloseButton: PropTypes.bool.isRequired,\r\n\tshowFeedbackPanel: PropTypes.bool.isRequired\r\n}\r\n\r\n\r\n// ExamOutroPage (connected to store)\r\n// -----------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\tconst examData = getExamData(store);\r\n\tconst examGuid = getGuid(examData);\r\n\r\n\tconst schedulesData = getSchedulesData(store);\r\n\tconst scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n\r\n\tconst proctorioData = getProctorioData(store);\r\n\r\n\treturn {\r\n\t\tshowCloseButton: !(getVersion(scheduleData) || (getProctoringMode(getClientSettingsData(getSettingsData(store))) == \"1\")),\r\n\t\tshowFeedbackPanel: shouldShowFeedback(examData),\r\n\t\tproctorioActive: getParent(proctorioData),\r\n\t}\r\n}\r\n\r\nExamOutroPage = connect(mapStoreToProps)(ExamOutroPage);\r\n\r\n\r\n// ExamOutroPage (EXPORT)\r\n// -----------------------------------------------------------------\r\nexport {ExamOutroPage}","\r\n// npm\r\nimport React from 'react'\r\n\r\n\r\nconst ExamRequestSaverContext = React.createContext();\r\n\r\n\r\nexport {ExamRequestSaverContext}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// react\r\nimport {ExamRequestSaverContext} from './context'\r\n\r\n\r\nconst withExamRequestSaver = (Component) =>\r\n{\r\n\treturn (props) => {\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{(examRequestSaver) => }\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n}\r\n\r\n\r\nexport {withExamRequestSaver}","\r\n// npm\r\nimport {milliseconds, seconds} from 'time-convert'\r\n\r\n\r\nconst getAllowedTime = (timeData) => (\r\n\tmilliseconds.from(seconds)(timeData.get('allowedTime'))\r\n)\r\n\r\nconst getWarningTime = (timeData) => (\r\n\tmilliseconds.from(seconds)(timeData.get('warningTime'))\r\n)\r\n\r\nconst getExamTimeRemaining = (timeData) => (\r\n\tmilliseconds.from(seconds)(timeData.get('examTimeRemaining'))\r\n)\r\n\r\nconst getReadingTimeRemaining = (timeData) => (\r\n\tmilliseconds.from(seconds)(timeData.get('readingTimeRemaining'))\r\n)\r\n\r\nconst getTimeRemaining = (timeData) => (\r\n\tgetExamTimeRemaining(timeData) + getReadingTimeRemaining(timeData)\r\n)\r\n\r\nconst getExamTime = (timeData) => (\r\n\tmilliseconds.from(seconds)(timeData.get('examTime'))\r\n)\r\n\r\nconst getReadingTime = (timeData) => (\r\n\tmilliseconds.from(seconds)(timeData.get('readingTime'))\r\n)\r\n\r\nconst canBypassReadingTime = (timeData) => (\r\n\ttimeData.get('canBypassReadingTime')\r\n)\r\n\r\nconst getExamElapsedTime = (timeData) => (\r\n\tMath.round(timeData.get('elapsedTime')/1000)\r\n)\r\n\r\n\r\nexport {\r\n\tgetExamTime,\r\n\tgetReadingTime,\r\n\tgetAllowedTime,\r\n\tgetWarningTime,\r\n\tgetTimeRemaining,\r\n\tgetExamTimeRemaining,\r\n\tgetReadingTimeRemaining,\r\n\tcanBypassReadingTime,\r\n\tgetExamElapsedTime\r\n}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport {milliseconds, seconds} from 'time-convert'\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\n\r\nimport { getTimeStamp } from \"utils/time-stamp\";\r\n\r\n// react (hocs)\r\nimport {withExamRequestSaver} from 'components/hocs/exam_request_saver'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {LoadingSpinner} from 'components/presentation/loading-spinner'\r\n\r\n// redux (selectors)\r\nimport {isOfflineMode} from 'redux/reducers/app/selectors'\r\nimport {getExamData, getAppData} from 'redux/reducers/selectors'\r\nimport {getTimeRemaining} from 'redux/reducers/exam/time/selectors'\r\nimport {getPaperPartId} from 'redux/reducers/exam/content/complex-selectors'\r\nimport {getSectionDataById} from 'redux/reducers/exam/content/sections/selectors'\r\nimport {getSectionsData, getCurrentQuestionId} from 'redux/reducers/exam/content/selectors'\r\nimport {getSectionTimeRemaining} from 'redux/reducers/exam/content/sections/section/selectors'\r\nimport {getTimeData, getContentData, getFormRunGuid, getScheduleGuid} from 'redux/reducers/exam/selectors'\r\nimport {getExamElapsedTime} from 'redux/reducers/exam/time/selectors'\r\n\r\n// redux (actions)\r\nimport {completeSchedule} from './actions'\r\n\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { MESSAGE_IDS, messageNotDefined } from \"constants/message-ids\";\r\n\r\n\r\n// ExamCompleter (not connected to store)\r\n// ----------------------------------------\r\n\r\nclass ExamCompleter extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tconst onSuccess = () => {\r\n\t\t\tthis.props.completeSchedule(this.props.scheduleGuid);\r\n\t\t\tthis.props.onComplete();\r\n\t\t}\r\n\r\n\t\tconst {formRunGuid, timeRemaining, examRequestSaver, elapsedTime} = this.props;\r\n\t\tlet examTimeRemaining = seconds.from(milliseconds)(timeRemaining);\r\n\t\texamTimeRemaining = Math.round(examTimeRemaining);\r\n\r\n\t\tconst ClientTime = getTimeStamp();\r\n\r\n\t\tconst payload = {formRunGuid, examTimeRemaining, ClientTime, elapsedTime};\r\n\r\n\t\texamRequestSaver.completeExam(payload, {onSuccess});\r\n\t}\r\n\r\n\trender() \r\n\t{\r\n\t\tconst {offline, messages} = this.props;\r\n\r\n\t\tif (!offline) {\r\n\t\t\tconst appBarProps = {\r\n\t\t\t\ttitle: \"Completing Exam\",\r\n\t\t\t\tloadingTitle: true,\r\n\t\t\t\tlogo: true,\r\n\t\t\t};\r\n\r\n\t\t\treturn (\r\n\t\t\t\t\r\n\t\t\t\t\t \r\n\t\t\t\t\t ;\r\n\t\t\t\t \r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\tlet title = messages[MESSAGE_IDS.ERRORS.EXAM_COMPLETER_OFFLINE_TITLE];\r\n\t\tlet message = messages[MESSAGE_IDS.ERRORS.EXAM_COMPLETER_OFFLINE_MESSAGE];\r\n\r\n\t\tif (messageNotDefined(title)){\r\n\t\t\ttitle=\"Oops. Your internet connection was lost\";\r\n\t\t}\r\n\r\n\t\tif (messageNotDefined(message)){\r\n\t\t\tmessage=\"Not to worry. We'll save your progress as soon as you're back online :)\";\r\n\t\t}\r\n\r\n\t\tconst popupProps = {\r\n\t\t\ttitle,\r\n\t\t\tcontent: {\r\n\t\t\t\ttext: message\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn ;\r\n\t}\r\n}\r\n\r\nExamCompleter.propTypes = {\r\n\toffline: PropTypes.bool.isRequired,\r\n\t//scheduleGuid: PropTypes.string.isRequired,\r\n\tformRunGuid: PropTypes.string.isRequired,\r\n\ttimeRemaining: PropTypes.number.isRequired,\r\n\tcompleteSchedule: PropTypes.func.isRequired,\r\n\tonComplete: PropTypes.func.isRequired,\r\n\tonFail: PropTypes.func.isRequired,\r\n\texamRequestSaver: PropTypes.shape({\r\n\t\tcompleteExam: PropTypes.func.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\n// ExamCompleter (connected to store)\r\n// ----------------------------------------\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\tconst offline = isOfflineMode(getAppData(store));\r\n\r\n\tconst examData = getExamData(store);\r\n\tconst contentData = getContentData(examData);\r\n\t\r\n\tconst currentQuestionId = getCurrentQuestionId(contentData);\r\n\tconst paperPartId = getPaperPartId(contentData)(currentQuestionId);\r\n\tconst elapsedTime = getExamElapsedTime(getTimeData(examData));\r\n\t\r\n\tif (paperPartId)\r\n\t{\r\n\t\tconst sectionsData = getSectionsData(contentData);\r\n\t\tconst paperPartData = getSectionDataById(sectionsData)(paperPartId);\r\n\t\t\r\n\t\treturn {\r\n\t\t\toffline,\r\n\t\t\tscheduleGuid: getScheduleGuid(getExamData(store)),\r\n\t\t\tformRunGuid: getFormRunGuid(getExamData(store)),\r\n\t\t\ttimeRemaining: getSectionTimeRemaining(paperPartData),\r\n\t\t\telapsedTime\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tconst timeData = getTimeData(examData);\r\n\t\t\r\n\t\treturn {\r\n\t\t\toffline,\r\n\t\t\tscheduleGuid: getScheduleGuid(getExamData(store)),\r\n\t\t\tformRunGuid: getFormRunGuid(getExamData(store)),\r\n\t\t\ttimeRemaining: getTimeRemaining(timeData),\r\n\t\t\telapsedTime\r\n\t\t}\r\n\t}\r\n}\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tcompleteSchedule: (scheduleGuid) => dispatch(completeSchedule(scheduleGuid))\r\n});\r\n\r\nExamCompleter = connect(mapStoreToProps, mapDispatchToProps)(ExamCompleter);\r\nExamCompleter = withExamRequestSaver(ExamCompleter);\r\nExamCompleter = withMessages(ExamCompleter);\r\n\r\n\r\n// EXPORT\r\n// ----------------------------------------\r\nexport {ExamCompleter}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\n\r\n// react (hocs)\r\nimport { withExamRequestSaver } from \"components/hocs/exam_request_saver\";\r\n\r\n// react\r\nimport { LoadingSpinner } from \"components/presentation/loading-spinner\";\r\nimport {\r\n activityLogger,\r\n ACTIVITIES,\r\n} from \"libs/activity_logger/activity-logger\";\r\nimport { displayActivityLogToConsole } from \"libs/activity_logger/activity-logger-helper\";\r\n\r\n// redux (selectors)\r\nimport { isOfflineMode } from \"redux/reducers/app/selectors\";\r\nimport { getExamData, getAppData } from \"redux/reducers/selectors\";\r\nimport { getFormRunGuid } from \"redux/reducers/exam/selectors\";\r\n\r\n// FinishActivityLogger (not connected to store)\r\n// ----------------------------------------\r\n\r\nclass FinishActivityLogger extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.timeoutTimer = 30 * 1000;\r\n this.handleTimeout = this.handleTimeout.bind(this);\r\n }\r\n\r\n componentDidMount() {\r\n const { formRunGuid, examRequestSaver, debugMode } = this.props;\r\n\r\n const onSuccess = () => {\r\n this.props.onComplete();\r\n // activityLogger.sent(formRunGuid).then(() => {\r\n // this.props.onComplete();\r\n // });\r\n };\r\n\r\n const onFail = () => {\r\n this.props.onComplete();\r\n };\r\n\r\n if (!activityLogger.isLogging()) {\r\n onSuccess();\r\n } else {\r\n this.timer = setTimeout(this.handleTimeout, this.timeoutTimer);\r\n\r\n activityLogger.log(ACTIVITIES.FINISH_EXAM, {}).then(\r\n activityLogger.get(formRunGuid).then((activities) => {\r\n if (!debugMode) {\r\n const payload = { formRunGuid, activities };\r\n examRequestSaver.saveActivityLog(payload, {\r\n onSuccess,\r\n onFail,\r\n });\r\n } else {\r\n displayActivityLogToConsole(ACTIVITIES, {\r\n activities,\r\n exclude: [\r\n ACTIVITIES.LOCAL_ANSWER,\r\n ACTIVITIES.SERVER_ANSWER,\r\n ],\r\n });\r\n onSuccess();\r\n }\r\n })\r\n );\r\n }\r\n }\r\n\r\n componentWillUnmount() {\r\n clearTimeout(this.timer);\r\n }\r\n\r\n handleTimeout() {\r\n this.props.onComplete();\r\n }\r\n\r\n render() {\r\n const appBarProps = {\r\n title: \"Completing Activity Log\",\r\n loadingTitle: true,\r\n logo: true,\r\n };\r\n \r\n return (\r\n \r\n \r\n ;\r\n \r\n );\r\n }\r\n}\r\n\r\nFinishActivityLogger.propTypes = {\r\n offline: PropTypes.bool.isRequired,\r\n formRunGuid: PropTypes.string.isRequired,\r\n onComplete: PropTypes.func.isRequired,\r\n onFail: PropTypes.func.isRequired,\r\n};\r\n\r\n// FinishActivityLogger (connected to store)\r\n// ----------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const appData = getAppData(store);\r\n\r\n return {\r\n offline: isOfflineMode(appData),\r\n formRunGuid: getFormRunGuid(examData),\r\n debugMode: false,\r\n };\r\n};\r\n\r\nFinishActivityLogger = connect(mapStoreToProps)(FinishActivityLogger);\r\nFinishActivityLogger = withExamRequestSaver(FinishActivityLogger);\r\n\r\n// EXPORT\r\n// ----------------------------------------\r\nexport { FinishActivityLogger };\r\n","import React, { Component } from 'react';\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getGuid } from \"redux/reducers/exam/selectors\";\r\nimport { getExamData, getSchedulesData } from \"redux/reducers/selectors\";\r\nimport { getScheduleDataByExamGuid } from \"redux/reducers/schedules/selectors\";\r\nimport { getProctorProvider } from 'redux/reducers/schedules/schedule/selectors';\r\n\r\nimport { PROCTORING_TYPES } from 'constants/proctoring';\r\n\r\nclass CloseIntegrityAdvocate extends Component {\r\n state = {}\r\n\r\n componentDidMount() {\r\n const { onComplete } = this.props;\r\n\r\n if (this.isIntegrityAdvocate()) {\r\n try{\r\n console.log('Closing Integrity Advocate');\r\n\r\n window.IntegrityAdvocate.endSession();\r\n\r\n let elem = window.document.getElementById('integrityadvocate_container');\r\n if (elem !== null && elem !== undefined) {\r\n elem.remove();\r\n }\r\n elem = window.document.getElementById('integrityadvocate_tab');\r\n if (elem !== null && elem !== undefined) {\r\n elem.remove();\r\n }\r\n }\r\n catch(e){\r\n console.log(\"Unable to close IA\", e); \r\n }\r\n }\r\n\r\n onComplete();\r\n }\r\n\r\n isIntegrityAdvocate() {\r\n const { proctoringProvider } = this.props;\r\n\r\n return proctoringProvider === PROCTORING_TYPES.INTEGRITY_ADVOCATE;\r\n }\r\n\r\n render() {\r\n return null;\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const examGuid = getGuid(examData);\r\n\r\n const schedulesData = getSchedulesData(store);\r\n const scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n const proctoringProvider = getProctorProvider(scheduleData)\r\n\r\n\r\n return { proctoringProvider }\r\n}\r\n\r\nCloseIntegrityAdvocate = connect(mapStoreToProps)(CloseIntegrityAdvocate);\r\n\r\nexport { CloseIntegrityAdvocate };","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\n// react\r\nimport { LoadingSpinner } from \"components/presentation/loading-spinner\";\r\n\r\n// redux (selectors)\r\nimport {\r\n getExamData,\r\n getSessionData,\r\n getSettingsData,\r\n} from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport {\r\n shouldShowFeedback,\r\n getFormRunGuid,\r\n} from \"redux/reducers/exam/selectors\";\r\nimport {\r\n getClientSessionData,\r\n getUserSessionData,\r\n} from \"redux/reducers/session/selectors\";\r\nimport {\r\n getUserGuid,\r\n getLanguageId,\r\n} from \"redux/reducers/session/user/selectors\";\r\nimport { getOrgId } from \"redux/reducers/session/client/selectors\";\r\nimport { getIsDownloadablePracticeExam } from \"redux/reducers/settings/client/selectors\";\r\n\r\n// redux (actions)\r\nimport { setExamResult, setFormRunId } from \"./actions\";\r\n\r\n// other\r\nimport { messagesApi } from \"libs/api/interface/api-messages\";\r\nimport { assessmentApi } from \"libs/api/interface/api-assessment\";\r\n\r\nconst hasExamBeenCompleted = (parsedResponse) => {\r\n //return parsedResponse.results && parsedResponse.results[0] && parsedResponse.results[0].Completed;\r\n return (\r\n parsedResponse.results &&\r\n parsedResponse.results[0] &&\r\n parsedResponse.results[0].Processed\r\n );\r\n};\r\n\r\n// ResultFetcher (not connected to store)\r\n// ----------------------------------------\r\n\r\nclass ResultFetcher extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.tryCounter = 0;\r\n this.tryCounterMax = 6;\r\n this.tryDelay = 10000;\r\n\r\n this.initializeBoundMethods();\r\n }\r\n\r\n initializeBoundMethods() {\r\n const saveExamResult = (result, message = \"\") => {\r\n const {\r\n isDownloadablePracticeExam,\r\n onFinish,\r\n onComplete,\r\n setFormRunId,\r\n setExamResult,\r\n } = this.props;\r\n\r\n setExamResult({\r\n marks: result.score,\r\n totalMarks: result.maxScore,\r\n score: result.percentScore,\r\n grade: result.grade,\r\n passed: result.pass,\r\n message: message || \"\",\r\n allowImmediateResit: result.immediateResitAvailable,\r\n });\r\n\r\n setFormRunId(result.formRunID);\r\n\r\n if (isDownloadablePracticeExam) {\r\n onComplete();\r\n } else {\r\n onFinish();\r\n }\r\n };\r\n\r\n const saveResult = (result) => {\r\n const { orgId, userGuid, languageId } = this.props;\r\n const { feedbackMessage, feedbackMessageID } = result;\r\n\r\n if (check.nonEmptyString(feedbackMessage)) {\r\n saveExamResult(result, feedbackMessage);\r\n } else if (check.assigned(feedbackMessageID)) {\r\n messagesApi\r\n .requestMessages(\r\n [feedbackMessageID],\r\n orgId,\r\n userGuid,\r\n languageId\r\n )\r\n .then((response) => {\r\n const messages = JSON.parse(response);\r\n const message = check.nonEmptyArray(messages)\r\n ? messages[0]\r\n : \"\";\r\n\r\n saveExamResult(result, message);\r\n })\r\n .catch(() => saveExamResult(result, \"\"));\r\n } else {\r\n saveExamResult(result, \"\");\r\n }\r\n };\r\n\r\n const resultStillBeingProcessed = () => {\r\n const {\r\n orgId,\r\n userGuid,\r\n languageId,\r\n setExamResult,\r\n onFinish,\r\n onComplete,\r\n isDownloadablePracticeExam,\r\n } = this.props;\r\n const messageId = \"ResultStillBeingProcessed\";\r\n const defaultMessage = \"Result still being processed\";\r\n\r\n messagesApi\r\n .requestMessages([messageId], orgId, userGuid, languageId)\r\n .then((response) => {\r\n const messages = JSON.parse(response);\r\n const message = check.nonEmptyArray(messages)\r\n ? messages[0]\r\n : defaultMessage;\r\n\r\n setExamResult({\r\n message,\r\n resultStillBeingProcessed: true,\r\n });\r\n\r\n if (isDownloadablePracticeExam) {\r\n onComplete();\r\n } else {\r\n onFinish();\r\n }\r\n });\r\n };\r\n\r\n const trySaveResult = (response) => {\r\n const parsedResponse = JSON.parse(response);\r\n\r\n if (parsedResponse.marked) {\r\n saveResult(parsedResponse);\r\n } else {\r\n if (this.tryCounter === this.tryCounterMax) {\r\n resultStillBeingProcessed();\r\n } else {\r\n this.tryCounter++;\r\n this.tryFetchResults();\r\n }\r\n }\r\n };\r\n\r\n const trySaveComplete = (response) => {\r\n const parsedResponse = JSON.parse(response);\r\n\r\n if (hasExamBeenCompleted(parsedResponse)) {\r\n const { shouldFetchResults, onComplete } = this.props;\r\n\r\n if (shouldFetchResults) {\r\n this.tryFetchResults();\r\n } else {\r\n onComplete();\r\n }\r\n } else {\r\n this.tryExamComplete();\r\n }\r\n };\r\n\r\n this.tryFetchResults = () => {\r\n setTimeout(() => {\r\n assessmentApi\r\n .getResult(this.props.formRunGuid)\r\n .then(trySaveResult)\r\n .catch(this.props.onError);\r\n }, this.tryDelay);\r\n };\r\n\r\n this.tryExamComplete = () => {\r\n setTimeout(() => {\r\n assessmentApi\r\n .isComplete(this.props.formRunGuid)\r\n .then(trySaveComplete)\r\n .catch(this.props.onError);\r\n }, this.tryDelay);\r\n };\r\n }\r\n\r\n componentDidMount() {\r\n const { shouldFetchResults, isDownloadablePracticeExam, onFinish } =\r\n this.props;\r\n\r\n if (isDownloadablePracticeExam) {\r\n this.tryExamComplete();\r\n } else if (shouldFetchResults) {\r\n this.tryFetchResults();\r\n } else {\r\n onFinish();\r\n }\r\n }\r\n\r\n render() {\r\n const { shouldFetchResults, isDownloadablePracticeExam } = this.props;\r\n const needToGetResults =\r\n shouldFetchResults || isDownloadablePracticeExam;\r\n\r\n if (!needToGetResults) return ;\r\n\r\n const appBarProps = {\r\n title: \"Getting your results\",\r\n loadingTitle: true,\r\n logo: true,\r\n };\r\n\r\n return (\r\n \r\n \r\n ;\r\n \r\n );\r\n }\r\n}\r\n\r\nResultFetcher.propTypes = {\r\n shouldFetchResults: PropTypes.bool.isRequired,\r\n formRunGuid: PropTypes.string.isRequired,\r\n setExamResult: PropTypes.func.isRequired,\r\n setFormRunId: PropTypes.func.isRequired,\r\n onFinish: PropTypes.func.isRequired,\r\n onError: PropTypes.func.isRequired,\r\n orgId: PropTypes.number.isRequired,\r\n languageId: PropTypes.number.isRequired,\r\n userGuid: PropTypes.string.isRequired,\r\n};\r\n\r\n// ResultFetcher (connected to store)\r\n// ----------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const sessionData = getSessionData(store);\r\n const userSessionData = getUserSessionData(sessionData);\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n return {\r\n shouldFetchResults: shouldShowFeedback(examData),\r\n formRunGuid: getFormRunGuid(examData),\r\n orgId: getOrgId(getClientSessionData(sessionData)),\r\n languageId: getLanguageId(userSessionData),\r\n userGuid: getUserGuid(userSessionData),\r\n isDownloadablePracticeExam:\r\n getIsDownloadablePracticeExam(clientSettingsData),\r\n };\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setExamResult: (data) => dispatch(setExamResult(data)),\r\n setFormRunId: (id) => dispatch(setFormRunId(id)),\r\n});\r\n\r\nResultFetcher = connect(mapStoreToProps, mapDispatchToProps)(ResultFetcher);\r\n\r\n// EXPORT\r\n// ----------------------------------------\r\nexport { ResultFetcher };\r\n","import React from \"react\";\r\n\r\nimport LinearProgress from \"@material-ui/core/LinearProgress\";\r\nimport Typography from \"@material-ui/core/Typography\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nconst styles = ({ spacing, palette }) => ({\r\n root: {\r\n width: \"100%\",\r\n cursor: \"default\",\r\n },\r\n topLine: {\r\n display: \"flex\",\r\n width: \"100%\",\r\n \"&>div\": {\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n },\r\n \"&>div:first-child\": {\r\n flexGrow: 1,\r\n paddingRight: spacing.unit * 2,\r\n \"&>div\": {\r\n width: `calc(100% - 16px)`,\r\n },\r\n },\r\n \"&>div:last-child\": {\r\n minWidth: \"35px\",\r\n textAlign: \"center\",\r\n },\r\n },\r\n bottomLine: {\r\n display: \"flex\",\r\n width: \"100%\",\r\n justifyContent: \"center\",\r\n paddingTop: spacing.unit * 2\r\n },\r\n});\r\n\r\nlet ProgressLabel = (props) => {\r\n const { value, classes, message } = props;\r\n //const message = \"Getting your downloadable results\";\r\n return (\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n {`${Math.round(value)}%`} \r\n
\r\n
\r\n
\r\n \r\n {message}\r\n \r\n
\r\n
\r\n );\r\n};\r\n\r\nProgressLabel = withStyles(styles)(ProgressLabel);\r\n\r\nexport { ProgressLabel };\r\n","import React, { Component } from \"react\";\r\nimport * as signalR from \"@microsoft/signalr\";\r\nimport moment from \"moment\";\r\nimport { check } from \"@xams-utils/check-types\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport {\r\n getExamData,\r\n getSettingsData,\r\n getSessionData,\r\n} from \"redux/reducers/selectors\";\r\nimport { getFormRunGuid } from \"redux/reducers/exam/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getHubUrl } from \"redux/reducers/settings/app/selectors\";\r\nimport { getIsDownloadablePracticeExam } from \"redux/reducers/settings/client/selectors\";\r\nimport { getPublicExamUserName } from \"redux/reducers/session/user/selectors\";\r\n\r\nimport {\r\n getUserSessionData,\r\n getClientSessionData,\r\n} from \"redux/reducers/session/selectors\";\r\nimport { getOrgId } from \"redux/reducers/session/client/selectors\";\r\n\r\nimport { getName, shouldShowFeedback } from \"redux/reducers/exam/selectors\";\r\n\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\nimport Card from \"@material-ui/core/Card\";\r\nimport CardContent from \"@material-ui/core/CardContent\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport CloudDownloadIcon from \"@material-ui/icons/CloudDownload\";\r\nimport ErrorOutlineIcon from \"@material-ui/icons/ErrorOutline\";\r\n\r\nimport { ProgressLabel } from \"./progress-label\";\r\n\r\nimport { OutroTitle } from \"../outro-title\";\r\n\r\nimport { ExamLayout } from \"components/pages/exam/_layout/layout\";\r\nimport { Align } from \"components/layout/align\";\r\n\r\nimport { getTimeStamp } from \"utils/time-stamp\";\r\nimport { errorApi } from \"libs/api/interface/api-error\";\r\n\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { MESSAGE_IDS, messageNotDefined } from \"constants/message-ids\";\r\n\r\nimport { LearnerFeedbackPanel } from \"components/pages/_exam/initialized/outro/page/learner-feedback-panel\";\r\n\r\nconst LOCAL_MESSAGES = {\r\n PREPARING_DOWNLOAD: \"Preparing Your Results For Downloading\",\r\n PREPARING_DOWNLOAD_TITLE: \"Preparing Results\",\r\n DOWNLOAD_RESULTS: \"Click Here to Download Your Results\",\r\n DOWNLOAD_RESULTS_TITLE: \"Download Results\",\r\n ERROR_TITLE: \"Unable to download results\",\r\n ERROR_MESSAGE: \"Sorry, we are unable to download your results at present\",\r\n};\r\n\r\nconst styles = ({ spacing, palette, breakpoints }) => ({\r\n root: {\r\n display: \"flex\",\r\n width: \"100%\",\r\n minHeight: \"32px\",\r\n \"&>div\": {\r\n cursor: \"pointer\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n },\r\n \"&>div:first-child\": {\r\n paddingRight: spacing.unit * 2,\r\n },\r\n \"&>div:last-child\": {\r\n width: \"100%\",\r\n },\r\n },\r\n select: {\r\n \"&>div\": {\r\n cursor: \"pointer\",\r\n },\r\n },\r\n noSelect: {\r\n \"&>div\": {\r\n cursor: \"default\",\r\n },\r\n },\r\n panel: {\r\n backgroundColor: palette.background.light,\r\n //maxWidth: 800,\r\n [breakpoints.down(820)]: {\r\n width: \"100%\",\r\n },\r\n [breakpoints.up(821)]: {\r\n width: 800,\r\n },\r\n marginTop: spacing.unit * 2,\r\n paddingTop: spacing.unit * 2,\r\n alignSelf: \"stretch\",\r\n },\r\n text: {\r\n fontSize: \"large\",\r\n },\r\n});\r\n\r\nconst getErrorObject = (errorObj) => {\r\n var obj = {};\r\n\r\n try {\r\n if (check.string(errorObj)) {\r\n return errorObj;\r\n }\r\n\r\n obj.text = errorObj.toString();\r\n if (check.assigned(errorObj.message)) {\r\n obj.message = errorObj.message;\r\n }\r\n\r\n if (errorObj) {\r\n for (let key in errorObj) {\r\n obj[key] = errorObj[key];\r\n }\r\n }\r\n } catch (e) {\r\n debugger;\r\n }\r\n\r\n return obj;\r\n};\r\n\r\nclass ResultDownloader extends Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.willSendErrorReport = true;\r\n\r\n this.invoked = false;\r\n this.connected = false;\r\n this.timedOutValue = 60000;\r\n this.startedTimeStamp = null;\r\n this.progressArray = [];\r\n this.tryStart = {\r\n max: 3,\r\n timeOut: 15000,\r\n tries: 0,\r\n };\r\n\r\n this.tryAgainTimer = null;\r\n this.timer = null;\r\n\r\n this.handleClick = this.handleClick.bind(this);\r\n this.setupSignalR = this.setupSignalR.bind(this);\r\n this.sendErrorReport = this.sendErrorReport.bind(this);\r\n this.timedOut = this.timedOut.bind(this);\r\n this.handleHubStartError = this.handleHubStartError.bind(this);\r\n this.startConnection = this.startConnection.bind(this);\r\n\r\n this.state = {\r\n url: null,\r\n progress: null,\r\n error: false,\r\n };\r\n }\r\n\r\n componentDidMount() {\r\n this.startConnection();\r\n }\r\n\r\n componentWillUnmount() {\r\n clearTimeout(this.timer);\r\n clearTimeout(this.tryAgainTimer);\r\n }\r\n\r\n startConnection() {\r\n this.startedTimeStamp = getTimeStamp();\r\n this.timer = setTimeout(this.timedOut, this.timedOutValue);\r\n this.setupSignalR();\r\n }\r\n\r\n timedOut() {\r\n clearTimeout(this.timer);\r\n clearTimeout(this.tryAgainTimer);\r\n\r\n this.sendErrorReport(\"Timed out\", \"Timed Out\");\r\n }\r\n\r\n setupSignalR() {\r\n const { hubUrl, orgId } = this.props;\r\n const method = \"practiceExamAnswersHub\";\r\n const url = `${hubUrl}${method}`;\r\n\r\n let connection = new signalR.HubConnectionBuilder();\r\n if (this.tryStart.tries > 0)\r\n connection = connection.withUrl(url, {\r\n transport: signalR.HttpTransportType.LongPolling,\r\n });\r\n else connection = connection.withUrl(url);\r\n connection = connection.build();\r\n\r\n connection.on(\"Progress\", (data) => {\r\n this.setState({ progress: data });\r\n this.progressArray.push({ timeStamp: getTimeStamp(), data });\r\n });\r\n\r\n connection.on(\"Link\", (data) => {\r\n clearTimeout(this.timer);\r\n this.setState({ url: data });\r\n });\r\n\r\n const { formRunGuid } = this.props;\r\n const fileName = this.getFileName();\r\n const methodInvoke = \"practiceExamAnswersByGUID\";\r\n\r\n connection\r\n .start()\r\n .then(() => {\r\n this.connected = true;\r\n connection\r\n .invoke(methodInvoke, formRunGuid, fileName, orgId)\r\n .then(() => {\r\n this.invoked = true;\r\n })\r\n .catch((e) => {\r\n this.sendErrorReport(\r\n e,\r\n `Trying to invoke method: ${methodInvoke}`\r\n );\r\n });\r\n })\r\n .catch((e) => {\r\n this.handleHubStartError(e, url);\r\n });\r\n\r\n this.setState({ progress: 0 });\r\n }\r\n\r\n handleHubStartError(error, url) {\r\n this.tryStart.tries++;\r\n const setError = this.tryStart.tries === this.tryStart.max;\r\n\r\n this.sendErrorReport(\r\n error,\r\n `Trying to start connection (#${this.tryStart.tries}): ${url}`,\r\n setError\r\n );\r\n\r\n if (!setError) {\r\n this.tryAgainTimer = setTimeout(\r\n this.startConnection,\r\n this.tryStart.timeOut\r\n );\r\n }\r\n }\r\n\r\n sendErrorReport(e, message = \"Error\", setError = true) {\r\n clearTimeout(this.timer);\r\n clearTimeout(this.tryAgainTimer);\r\n\r\n const { formRunGuid, publicUserName, examName } = this.props;\r\n const { progress } = this.state;\r\n\r\n const reason = {\r\n startedTimeStamp: this.startedTimeStamp,\r\n errorTimeStamp: getTimeStamp(),\r\n formRunGuid,\r\n connected: this.connected,\r\n invoked: this.invoked,\r\n progress,\r\n error: getErrorObject(e),\r\n userName: publicUserName,\r\n examName,\r\n progressArray: this.progressArray,\r\n };\r\n\r\n const payload = {\r\n reporterGuid: \"PRACTICE EXAM DOWNLOADER[V6/PLAYER]\",\r\n bugDescription: message,\r\n reason,\r\n };\r\n\r\n if (this.willSendErrorReport) {\r\n errorApi.sendBugReport(payload);\r\n } else {\r\n console.log(\"sendBugReport\", payload);\r\n console.log(\"Error\", e);\r\n }\r\n\r\n if (setError) {\r\n this.setState({ error: true });\r\n }\r\n }\r\n\r\n handleClick() {\r\n const { url } = this.state;\r\n\r\n const { onFinish } = this.props;\r\n window.open(url);\r\n onFinish();\r\n }\r\n\r\n getFileName() {\r\n const { examName } = this.props;\r\n //const examName='Maths 101 (MS111)';\r\n const today = moment();\r\n const title = encodeURIComponent(examName.replace(/\\s/g, \"_\"));\r\n return `${title}_${today.format(\"DD_MM_YYYY\")}.pdf`;\r\n }\r\n\r\n displayMessage() {\r\n const { classes, messages } = this.props;\r\n const { progress, url, error } = this.state;\r\n\r\n if (error) {\r\n let message = messages[MESSAGE_IDS.PRACTICE_DOWNLOAD.TIMED_OUT];\r\n\r\n if (messageNotDefined(message))\r\n message = LOCAL_MESSAGES.ERROR_MESSAGE;\r\n\r\n return (\r\n {message} \r\n );\r\n }\r\n if (url) {\r\n const message = LOCAL_MESSAGES.DOWNLOAD_RESULTS;\r\n\r\n return (\r\n {message} \r\n );\r\n } else {\r\n const progressProps = {\r\n value: progress,\r\n message: LOCAL_MESSAGES.PREPARING_DOWNLOAD,\r\n };\r\n\r\n return ;\r\n }\r\n }\r\n\r\n render() {\r\n const { classes, isDownloadablePracticeExam, shouldShowFeedback } =\r\n this.props;\r\n\r\n if (!isDownloadablePracticeExam) {\r\n return null;\r\n }\r\n const { url, error } = this.state;\r\n const divProps = {\r\n onClick: url && !error ? this.handleClick : null,\r\n };\r\n const divClass = `${classes.root} ${\r\n url ? classes.select : classes.noSelect\r\n }`;\r\n\r\n const icon = error ? (\r\n \r\n ) : (\r\n \r\n );\r\n\r\n const title = error\r\n ? LOCAL_MESSAGES.ERROR_TITLE\r\n : url\r\n ? LOCAL_MESSAGES.DOWNLOAD_RESULTS_TITLE\r\n : LOCAL_MESSAGES.PREPARING_DOWNLOAD_TITLE;\r\n\r\n const examLayoutProps = {\r\n bar: {\r\n content: (\r\n \r\n \r\n \r\n ),\r\n },\r\n centre: {\r\n content: (\r\n \r\n \r\n {shouldShowFeedback &&
}\r\n
\r\n \r\n \r\n
{icon}
\r\n
\r\n {this.displayMessage()}\r\n {/* Download Results */}\r\n
\r\n
\r\n \r\n \r\n
\r\n \r\n ),\r\n },\r\n };\r\n\r\n return ;\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n const appSettingsData = getAppSettingsData(settingsData);\r\n\r\n const sessionData = getSessionData(store);\r\n const userSessionData = getUserSessionData(sessionData);\r\n const clientSessionData = getClientSessionData(sessionData);\r\n\r\n return {\r\n formRunGuid: getFormRunGuid(examData),\r\n hubUrl: getHubUrl(appSettingsData),\r\n isDownloadablePracticeExam:\r\n getIsDownloadablePracticeExam(clientSettingsData),\r\n examName: getName(examData),\r\n publicUserName: getPublicExamUserName(userSessionData),\r\n orgId: getOrgId(clientSessionData),\r\n shouldShowFeedback: shouldShowFeedback(examData),\r\n };\r\n};\r\n\r\nResultDownloader = withMessages(withStyles(styles)(ResultDownloader));\r\nResultDownloader = connect(mapStoreToProps)(ResultDownloader);\r\n\r\n// https://test.xams.co.uk/learner/exam/demo?userid=491030&examid=228049&downloadResult=true\r\n// https://fs2019.xams.co.uk/learner/exam/demo?userid=832321&examid=227733&downloadResult=true\r\n\r\n// https://test.xams.co.uk/learner/exam/demo?userid=491030&examid=227733&downloadResult=true\r\n// https://v5-3.xams.co.uk/learner/exam/demo?&userid=774104&examID=154429&downloadResult=true\r\n// https://dev.xams.co.uk/learner/exam/demo?userid=829059&examid=315949&downloadResult=true\r\n\r\nexport { ResultDownloader };\r\n","\r\nimport {Machine} from 'xstate'\r\nimport {XStateConfig} from './xstate-config'\r\n\r\n\r\n// #########################################\r\n// STATE NAMES\r\n// #########################################\r\n\r\nconst STATES = {\r\n\tCOMPLETING_EXAM: 'completing_exam',\r\n\tFINISHING_ACTIVITY_LOGGER: 'finishing_activity_logger',\r\n\tCLOSING_INTEGRTITY_ADVOCATE: 'closing_integrity_advocate',\r\n\tFETCHING_RESULTS: 'fetching_results',\r\n\tDOWNLOADING_RESULTS: 'downloading_results',\r\n\tOUTRO_SCREEN: 'displaying_outro',\r\n\tNETWORK_ERROR: 'network_error'\r\n}\r\n\r\n\r\n// #########################################\r\n// EVENT NAMES\r\n// #########################################\r\n\r\nconst EVENTS = {\r\n\tERROR: 'error',\r\n\tFINISHED: 'success',\r\n\tGOT_COMPLETION: 'got.completion',\r\n\tEXAM_COMPLETED: 'exam.completed',\r\n\tRESULT_DOWNLOADED: 'result.downloaded',\r\n\tFINISHED_ACTIVITY_LOGGER: 'finish.activity_logger',\r\n\tCLOSED_INTEGRITY_ADVOCATE: 'closed_integrity_advocate'\r\n}\r\n\r\n\r\n// #########################################\r\n// STATES\r\n// #########################################\r\n\r\nconst finishingActivityLogger = new XStateConfig();\r\nfinishingActivityLogger.addTransition(EVENTS.FINISHED_ACTIVITY_LOGGER, STATES.CLOSING_INTEGRTITY_ADVOCATE);\r\nfinishingActivityLogger.addTransition(EVENTS.ERROR, STATES.NETWORK_ERROR);\r\n\r\nconst closingIntegrityAdvocate = new XStateConfig();\r\nclosingIntegrityAdvocate.addTransition(EVENTS.CLOSED_INTEGRITY_ADVOCATE, STATES.COMPLETING_EXAM);\r\n\r\nconst completingExam = new XStateConfig();\r\ncompletingExam.addTransition(EVENTS.EXAM_COMPLETED, STATES.FETCHING_RESULTS);\r\ncompletingExam.addTransition(EVENTS.ERROR, STATES.NETWORK_ERROR);\r\n\r\nconst fetchingResults = new XStateConfig();\r\nfetchingResults.addTransition(EVENTS.FINISHED, STATES.OUTRO_SCREEN);\r\nfetchingResults.addTransition(EVENTS.GOT_COMPLETION, STATES.DOWNLOADING_RESULTS);\r\nfetchingResults.addTransition(EVENTS.ERROR, STATES.NETWORK_ERROR);\r\n\r\nconst downloadingResults = new XStateConfig();\r\ndownloadingResults.addTransition(EVENTS.RESULT_DOWNLOADED, STATES.OUTRO_SCREEN);\r\ndownloadingResults.addTransition(EVENTS.ERROR, STATES.NETWORK_ERROR);\r\n\r\nconst displaying = new XStateConfig();\r\nconst networkError = new XStateConfig();\r\n\r\n\r\n// #########################################\r\n// MACHINE\r\n// #########################################\r\n\r\nconst _examOutro = new XStateConfig();\r\n_examOutro.initialState = STATES.FINISHING_ACTIVITY_LOGGER;\r\n_examOutro.addState(STATES.FINISHING_ACTIVITY_LOGGER, finishingActivityLogger);\r\n_examOutro.addState(STATES.CLOSING_INTEGRTITY_ADVOCATE, closingIntegrityAdvocate);\r\n_examOutro.addState(STATES.COMPLETING_EXAM, completingExam);\r\n_examOutro.addState(STATES.FETCHING_RESULTS, fetchingResults);\r\n_examOutro.addState(STATES.DOWNLOADING_RESULTS, downloadingResults);\r\n_examOutro.addState(STATES.OUTRO_SCREEN, displaying);\r\n_examOutro.addState(STATES.NETWORK_ERROR, networkError);\r\n\r\nconst machine = Machine(_examOutro.toObject());\r\nmachine.id = \"Exam Outro Machine\";\r\n\r\n\r\n// #########################################\r\n// EXPORT\r\n// #########################################\r\n\r\nconst examOutro = {\r\n\tmachine,\r\n\tEVENTS: {...EVENTS},\r\n\tSTATES: {...STATES}\r\n}\r\n\r\nexport {examOutro}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// xams-components\r\nimport {StateMachine, StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {ExamOutroPage} from './page'\r\nimport {ExamCompleter} from './exam-completer'\r\nimport {FinishActivityLogger} from './finish-activity-logger'\r\nimport { CloseIntegrityAdvocate } from './close-integrity-advocate'\r\nimport {ResultFetcher} from './result-fetcher'\r\nimport { ResultDownloader } from './page/download_results/result-downloader'\r\nimport {SetNetworkError} from 'components/pages/network_error/set-network-error'\r\n\r\n// machines\r\nimport {examOutro} from 'machines/exam-outro'\r\n\r\n\r\nconst {STATES, EVENTS} = examOutro;\r\n\r\n\r\nconst ExamOutroPageMachine = () =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{{\r\n\t\t\t\t\t[STATES.FINISHING_ACTIVITY_LOGGER]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.CLOSING_INTEGRTITY_ADVOCATE]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t[STATES.COMPLETING_EXAM]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.FETCHING_RESULTS]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.DOWNLOADING_RESULTS]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\t\t\t\t\t\r\n\t\t\t\t\t[STATES.OUTRO_SCREEN]: () => (\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.NETWORK_ERROR]: () => (\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t)\r\n\t\t\t\t}}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {ExamOutroPageMachine}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {ExamOutroPageMachine} from './machine'\r\n\r\n\r\nconst ExamOutro = () =>\r\n{\r\n\treturn ;\r\n}\r\n\r\n\r\nexport {ExamOutro}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// utils\r\nimport {assessmentApi} from 'libs/api/interface/api-assessment'\r\n\r\n// react\r\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\r\n\r\n// redux (selectors)\r\nimport {getUserGuid, getPublicExamUserName} from 'redux/reducers/session/user/selectors'\r\nimport {getUserSessionData} from 'redux/reducers/session/selectors'\r\nimport {getScheduleGuid, getGuid} from 'redux/reducers/exam/selectors'\r\nimport {getScheduleDataByExamGuid} from 'redux/reducers/schedules/selectors'\r\nimport {getSessionData, getSchedulesData, getExamData, getSettingsData} from 'redux/reducers/selectors'\r\nimport {getScheduleType, getExamGuid, getExamTypeGuid, getVersion} from 'redux/reducers/schedules/schedule/selectors'\r\n\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getAppVersion } from \"redux/reducers/settings/app/selectors\";\r\n\r\n// redux (actions)\r\nimport * as actions from './actions'\r\n\r\n\r\n// ExamStarter (not connected to store)\r\n// ----------------------------------------\r\n\r\nclass ExamStarter extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.onSuccess = (response) => { \r\n\t\t\tlet {formRunGUID:formRunGuid} = JSON.parse(response);\r\n\t\t\tthis.props.setFormRunGuid(formRunGuid);\r\n\t\t\tthis.props.commenceSchedule(this.props.scheduleGuid);\r\n\t\t\tthis.props.onStarted();\r\n\t\t}\r\n\t}\r\n\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tconst args = [\r\n\t\t\tthis.props.userGuid,\r\n\t\t\tthis.props.scheduleGuid,\r\n\t\t\tthis.props.scheduleType,\r\n\t\t\tthis.props.examGuid,\r\n\t\t\tthis.props.examTypeGuid,\r\n\t\t\tthis.props.version,\r\n\t\t\tthis.props.publicExamUserName,\r\n\t\t\tthis.props.appVersion\r\n\t\t]\r\n\r\n\t\tassessmentApi.startExam(...args)\r\n\t\t.then(this.onSuccess, this.props.onFail);\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn ;\r\n\t}\r\n}\r\n\r\nExamStarter.propTypes = {\r\n\tuserGuid: PropTypes.string.isRequired,\r\n\tscheduleGuid: PropTypes.string.isRequired,\r\n\tscheduleType: PropTypes.string.isRequired,\r\n\texamGuid: PropTypes.string.isRequired,\r\n\texamTypeGuid: PropTypes.string.isRequired,\r\n\tversion: PropTypes.oneOfType([\r\n\t\tPropTypes.string,\r\n\t\tPropTypes.number\r\n\t ]),\r\n\tonStarted: PropTypes.func.isRequired,\r\n\tonFail: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// ExamStarter (connected to store)\r\n// ----------------------------------------\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\tconst examData = getExamData(store);\r\n\tconst examGuid = getGuid(examData);\r\n\tconst scheduleGuid = getScheduleGuid(examData);\r\n\r\n\tconst schedulesData = getSchedulesData(store);\r\n\tconst scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n\r\n\tconst sessionData = getSessionData(store);\r\n\tconst userSessionData = getUserSessionData(sessionData);\t\r\n\tconst userGuid = getUserGuid(userSessionData);\r\n\tconst publicExamUserName = getPublicExamUserName(userSessionData);\r\n\r\n\tconst scheduleType = getScheduleType(scheduleData);\r\n\tconst examTypeGuid = getExamTypeGuid(scheduleData);\r\n\tconst version = getVersion(scheduleData);\r\n\r\n\tconst appSettingsData = getAppSettingsData(getSettingsData(store));\r\n\tconst appVersion = getAppVersion(appSettingsData);\r\n\r\n\treturn {userGuid, scheduleGuid, scheduleType, examGuid, examTypeGuid, version, publicExamUserName, appVersion};\r\n}\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tsetFormRunGuid: (formRunGuid) => dispatch(actions.setFormRunGuid(formRunGuid)),\r\n\tcommenceSchedule: (scheduleGuid) => dispatch(actions.commenceSchedule(scheduleGuid))\r\n});\r\n\r\nExamStarter = connect(mapStoreToProps, mapDispatchToProps)(ExamStarter);\r\n\r\n\r\n// EXPORT\r\n// ----------------------------------------\r\nexport {ExamStarter}","\r\n// redux (action-types)\r\nimport {SET_FORMRUN_GUID} from 'redux/reducers/exam/action-types'\r\nimport {SET_COMMENCED} from 'redux/reducers/schedules/schedule/action-types'\r\n\r\n\r\nconst setFormRunGuid = (value) => ({type: SET_FORMRUN_GUID, value});\r\nconst commenceSchedule = (guid) => ({type: SET_COMMENCED, guid});\r\n\r\n\r\nexport {setFormRunGuid, commenceSchedule}","import React from \"react\";\r\n\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport { withMessages } from \"components/hocs/messages\";\r\n\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport PauseIcon from \"@material-ui/icons/Pause\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nconst styles = (theme) => ({\r\n title: {\r\n display: \"flex\",\r\n justifyContent: \"left\",\r\n \"&>div:nth-child(2)\": {\r\n marginTop: \".5em\",\r\n marginLeft: theme.spacing.unit * 2,\r\n },\r\n },\r\n message: {\r\n marginTop: theme.spacing.unit * 2,\r\n },\r\n});\r\n\r\nlet PauseMessage = (props) => {\r\n const { classes, messages } = props;\r\n\r\n const title = messages[MESSAGE_IDS.EXAM.PAUSED_TITLE];\r\n const message = messages[MESSAGE_IDS.EXAM.PAUSED];\r\n\r\n return (\r\n \r\n
\r\n
\r\n
\r\n \r\n {title} \r\n \r\n
\r\n
\r\n
\r\n {message} \r\n
\r\n
\r\n );\r\n};\r\n\r\nPauseMessage = withStyles(styles)(PauseMessage);\r\nPauseMessage = withMessages(PauseMessage);\r\n\r\nexport { PauseMessage };\r\n","import React, { Component } from \"react\";\r\nimport { withMessages } from \"components/hocs/messages\";\r\n\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport PauseIcon from \"@material-ui/icons/Pause\";\r\nimport PlayArrowIcon from \"@material-ui/icons/PlayArrow\";\r\nimport Typography from \"@material-ui/core/Typography\";\r\n\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\nimport { Button } from \"@material-ui/core\";\r\n\r\nconst styles = (theme) => ({\r\n root: {\r\n display: \"flex\",\r\n width: \"100%\",\r\n justifyContent: \"left\",\r\n \"&>div\": {\r\n flex: 1,\r\n },\r\n \"&>div:first-child\": {\r\n width: \"50%\",\r\n },\r\n \"&>div:last-child\": {\r\n marginLeft: theme.spacing.unit,\r\n width: `calc(100% - ${theme.spacing.unit * 2}px)`,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n },\r\n },\r\n title: {\r\n display: \"flex\",\r\n justifyContent: \"left\",\r\n \"&>div:nth-child(2)\": {\r\n marginTop: \".5em\",\r\n marginLeft: theme.spacing.unit * 2,\r\n },\r\n },\r\n message: {\r\n marginTop: theme.spacing.unit * 2,\r\n },\r\n image: {\r\n position: \"relative\",\r\n // \"&>div:nth-child(1)\":{\r\n // position: \"absolute\",\r\n // left: 0,\r\n // top: 0,\r\n // },\r\n \"&>div:nth-child(2)\": {\r\n position: \"absolute\",\r\n left: 97,\r\n top: 250,\r\n width: 210,\r\n textAlign: \"center\",\r\n // border:\"1px black solid\"\r\n },\r\n },\r\n confirmation: {\r\n display: \"flex\",\r\n justifyContent: \"right\",\r\n marginTop: \"auto\",\r\n },\r\n});\r\n\r\nclass PauseImage extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.showImage=false;\r\n\r\n this.handleConfirmation = this.handleConfirmation.bind(this);\r\n }\r\n\r\n displayImage() {\r\n if (!this.showImage){\r\n return null;\r\n }\r\n\r\n const { classes, confirmExamResume, messages } = this.props;\r\n const messageID=confirmExamResume ? MESSAGE_IDS.GENERAL.RESUME:MESSAGE_IDS.GENERAL.PAUSED;\r\n const text = confirmExamResume ? messages[MESSAGE_IDS.GENERAL.RESUME] : messages[MESSAGE_IDS.GENERAL.PAUSED];\r\n\r\n return (\r\n \r\n
\r\n
\r\n
\r\n
\r\n \r\n {text}\r\n \r\n
\r\n
\r\n );\r\n }\r\n\r\n displayTitle() {\r\n const { classes, confirmExamResume, messages } = this.props;\r\n const title = confirmExamResume\r\n ? messages[MESSAGE_IDS.EXAM.CONFIRM_RESUME_TITLE]\r\n : messages[MESSAGE_IDS.EXAM.PAUSED_TITLE];\r\n const icon = confirmExamResume ? : ;\r\n\r\n return (\r\n \r\n
\r\n
\r\n \r\n {title} \r\n \r\n
\r\n
\r\n );\r\n }\r\n\r\n displayMessage() {\r\n const { confirmExamResume, messages, classes } = this.props;\r\n\r\n const message = confirmExamResume\r\n ? messages[MESSAGE_IDS.EXAM.CONFIRM_RESUME]\r\n : messages[MESSAGE_IDS.EXAM.PAUSED];\r\n\r\n return (\r\n \r\n {message} \r\n
\r\n );\r\n }\r\n\r\n displayConfirmation() {\r\n const { confirmExamResume, classes, messages } = this.props;\r\n \r\n if (!confirmExamResume) {\r\n return null;\r\n }\r\n\r\n const text=messages[MESSAGE_IDS.GENERAL.RESUME];\r\n const buttonProps = {\r\n variant: \"contained\",\r\n color: \"primary\",\r\n onClick: this.handleConfirmation,\r\n };\r\n\r\n return (\r\n \r\n {text} \r\n
\r\n );\r\n }\r\n\r\n handleConfirmation() {\r\n const { onConfirmation } = this.props;\r\n\r\n onConfirmation();\r\n }\r\n\r\n render() {\r\n const { classes } = this.props;\r\n\r\n return (\r\n \r\n {this.displayImage()}\r\n
\r\n {this.displayTitle()}\r\n {this.displayMessage()}\r\n {this.displayConfirmation()}\r\n
\r\n
\r\n );\r\n }\r\n}\r\n\r\nPauseImage = withStyles(styles)(PauseImage);\r\nPauseImage = withMessages(PauseImage);\r\n\r\nexport { PauseImage };\r\n","import React, { Component } from 'react';\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getConfirmExamResume } from \"redux/reducers/exam/selectors\";\r\nimport { PAUSE_EXAM, RESUME_EXAM, CONFIRM_RESUME_EXAM } from \"redux/reducers/exam/action-types\";\r\n\r\nimport { Align } from \"components/layout/align\";\r\n\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nimport {PauseMessage} from \"./pause-message\"\r\nimport {PauseImage} from \"./pause-image\"\r\n\r\nconst styles = (theme) => ({\r\n paper: {\r\n flexGrow: 1,\r\n maxWidth: 800,\r\n padding: theme.spacing.unit * 2,\r\n margin: theme.spacing.unit * 2,\r\n backgroundColor: theme.palette.background.default,\r\n },\r\n title: {\r\n display: \"flex\",\r\n justifyContent: \"left\",\r\n \"&>div:nth-child(2)\": {\r\n marginTop: \".5em\",\r\n marginLeft: theme.spacing.unit * 2,\r\n },\r\n },\r\n message: {\r\n marginTop: theme.spacing.unit * 2,\r\n },\r\n});\r\n\r\nconst setExamPaused = (value) => ({\r\n type: value ? PAUSE_EXAM : RESUME_EXAM,\r\n});\r\n\r\nconst setConfirmResumeExam = (value) => ({\r\n type: CONFIRM_RESUME_EXAM,\r\n value,\r\n});\r\n\r\nclass ExamPausedPage extends Component {\r\n state = { } \r\n\r\n constructor(props){\r\n super(props);\r\n\r\n this.handleConfirmation=this.handleConfirmation.bind(this);\r\n }\r\n\r\n handleConfirmation(){\r\n const {confirmResumeExam, setExamPaused}=this.props;\r\n\r\n confirmResumeExam(false);\r\n setExamPaused(false);\r\n }\r\n\r\n render() { \r\n const { classes, confirmExamResume } = this.props;\r\n const props={confirmExamResume, onConfirmation: this.handleConfirmation}\r\n console.log('confirmExamResume', confirmExamResume)\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const confirmExamResume = getConfirmExamResume(examData);\r\n\r\n return { confirmExamResume };\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setExamPaused: (value) => dispatch(setExamPaused(value)),\r\n confirmResumeExam: (value) => dispatch(setConfirmResumeExam(value)),\r\n});\r\n\r\nExamPausedPage = connect(mapStoreToProps, mapDispatchToProps)(ExamPausedPage);\r\n\r\nExamPausedPage = withStyles(styles)(ExamPausedPage);\r\n\r\n\r\nexport { ExamPausedPage };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { isExamPaused } from \"redux/reducers/exam/selectors\";\r\n\r\nclass ExamPauseManager extends Component {\r\n state = {};\r\n\r\n componentDidMount() {\r\n const { paused, onPause } = this.props;\r\n\t\t\r\n if (paused && onPause) {\r\n onPause();\r\n }\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n const { paused: prevPaused } = prevProps;\r\n const { paused, onPause, onUnpause } = this.props;\r\n\r\n if (paused !== prevPaused) {\r\n\t\t\tconsole.log('ExamPauseManager - Prev Paused',prevPaused,'Paused',paused);\r\n\r\n if (paused && onPause) {\r\n\t\t\t\tconsole.log('ExamPauseManager - onPause')\r\n onPause();\r\n } else if (onUnpause) {\r\n\t\t\t\tconsole.log('ExamPauseManager - onUnpause')\r\n onUnpause();\r\n }\r\n }\r\n }\r\n\r\n render() {\r\n return null;\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const paused = isExamPaused(examData);\r\n\r\n return { paused };\r\n};\r\n\r\nExamPauseManager = connect(mapStoreToProps)(ExamPauseManager);\r\n\r\nexport { ExamPauseManager };\r\n","\r\n// npm\r\nimport React from 'react'\r\n\r\n// material-ui\r\nimport ArrowForwardIcon from '@material-ui/icons/ArrowForward'\r\n\r\n// react\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {ActionButton} from 'components/layout/action_button/button'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst resumeMessageId = MESSAGE_IDS.GENERAL.RESUME;\r\n\r\n\r\nlet ResumeButton = ({onClick, messages}) => (\r\n\t\r\n\t\t{messages[resumeMessageId]}\r\n\t \r\n)\r\n\r\n\r\nResumeButton = withMessages(ResumeButton);\r\n\r\n\r\nexport {ResumeButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react (layout)\r\nimport {ContentBarTitle} from 'components/pages/exam/_layout/content/bar/title'\r\n\r\n// react (presentation)\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst examInstructionsMessageId = MESSAGE_IDS.EXAM.INSTRUCTIONS;\r\n\r\n\r\nlet InstructionsTitle = ({messages}) => (\r\n\t\r\n\t\t{messages[examInstructionsMessageId]}\r\n\t \r\n)\r\n\r\n\r\nInstructionsTitle = withMessages(InstructionsTitle);\r\n\r\n\r\nexport {InstructionsTitle}","const timerLog = [];\r\nlet timerLogOff = false;\r\n\r\nconst formatNumber = (number, format) => {\r\n const st = \"\" + number;\r\n const size = st.length;\r\n const add = format.length - size;\r\n\r\n return add > 0 ? `${format.charAt(0).repeat(add)}${number}` : number;\r\n};\r\n\r\nconst displayDateTime = (timeStamp) => {\r\n const date = new Date(timeStamp);\r\n\r\n const hours = formatNumber(date.getHours(), \"00\");\r\n const minutes = formatNumber(date.getMinutes(), \"00\");\r\n const seconds = formatNumber(date.getSeconds(), \"00\");\r\n const milliseconds = formatNumber(date.getMilliseconds(), \"000\");\r\n\r\n return `${hours}:${minutes}:${seconds}:${milliseconds}`;\r\n};\r\n\r\nconst displayTime = (duration) => {\r\n const ms = 1000;\r\n\r\n var milliseconds = Math.floor(duration % 1000),\r\n seconds = Math.floor((duration / 1000) % 60),\r\n minutes = Math.floor((duration / (1000 * 60)) % 60),\r\n hours = Math.floor((duration / (1000 * 60 * 60)) % 24);\r\n\r\n return `${duration} = ${hours > 0 ? ` ${hours} h ` : \"\"}${\r\n hours > 0 || minutes > 0 ? ` ${minutes} m ` : \"\"\r\n }${\r\n hours > 0 || minutes > 0 || seconds > 0 ? ` ${seconds} s ` : \"\"\r\n }${milliseconds} ms`;\r\n // return `${milliseconds} = ${hours>0?` ${hours} h `:''}${hours>0 || minutes>0?` ${minutes} m `:''}${hours>0 || minutes>0 || seconds>0?` ${seconds} s `:''}${rest} ms';\r\n};\r\n\r\nconst addLog = (log1 = null, log2 = null, log3 = null) => {\r\n if (log1 === null) {\r\n displayLog();\r\n } else {\r\n var log = [];\r\n\r\n if (log1 !== null) log.push(log1);\r\n if (log2 !== null) log.push(log2);\r\n if (log3 !== null) log.push(log3);\r\n\r\n displayLogLine(log);\r\n timerLog.push(log);\r\n }\r\n};\r\nconst displayLog = () => {\r\n console.clear();\r\n timerLog.forEach((log) => {\r\n displayLogLine(log);\r\n });\r\n};\r\nconst displayLogLine = (log) => {\r\n if (log.length === 1) console.log(log[0]);\r\n else if (log.length === 2) console.log(log[0], log[1]);\r\n else if (log.length === 3) console.log(log[0], log[1], log[2]);\r\n};\r\n\r\nexport { addLog, displayDateTime, displayTime };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport reactTimer from 'react-timer-hoc'\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getLeaveExam, getResumeExam } from \"redux/reducers/exam/selectors\";\r\n\r\nimport {\r\n activityLogger,\r\n ACTIVITIES,\r\n} from \"libs/activity_logger/activity-logger\";\r\n\r\nimport {addLog, displayDateTime,displayTime} from './timer-debug'\r\n\r\n\r\nconst getElapsedDateTime = (lastDateTime, currentDateTime) => {\r\n\tlet elapsedDateTime = currentDateTime - lastDateTime;\r\n\tif (elapsedDateTime < 0) {\r\n\t\telapsedDateTime = 0\r\n\t}\r\n\treturn elapsedDateTime;\r\n}\r\n\r\n// Timer (not connected to reactTimer)\r\n// -------------------------------------------------------\r\n\r\nclass Timer extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tconst resumeTime = props.resumeTime || props.startTime;\r\n\r\n\t\tthis.state = {\r\n\t\t\ttime: resumeTime,\r\n\t\t\t// cache constant props\r\n\t\t\tresumeTime: resumeTime,\r\n\t\t\tstartTime: props.startTime,\r\n\t\t\tendTime: props.endTime,\r\n\t\t\ttickTime: props.tickTime,\r\n\t\t\tdirection: props.direction,\r\n\t\t\tticker: null\r\n\t\t};\r\n\r\n\t\tthis.lastDateTime = null;\r\n\r\n\t\tthis.finished = this.isFinished();\r\n\r\n\t\tthis.lastDateTime = null;\r\n this.debugTimerCounter = 0;\r\n\t}\r\n\r\n\tcomponentWillUnmount(){\r\n\t\tif (this.props.timer) this.props.timer.stop();\r\n\t}\r\n\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tif (!this.finished)\r\n\t\t{\r\n\t\t\tthis.validateTimerDirection();\r\n\t\t\tthis.timerStartDt = this.getDateTime();\r\n\t\t\tthis.props.timer.setDelay(this.state.tickTime);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (this.props.timer) this.props.timer.stop();\r\n\t\t\tthis.props.onFinish();\r\n\t\t}\r\n\t}\r\n\r\n\tvalidateTimerDirection()\r\n\t{\r\n\t\tconst {endTime, startTime, direction} = this.state;\r\n\t\tconst timeToElapse = endTime - startTime;\r\n\t\tconst expectedDirection = Math.round(timeToElapse / Math.abs(timeToElapse));\r\n\t\tif (expectedDirection !== direction) {\r\n\t\t\tthrow `timer direction is not representative of start/end times. start: ${startTime}, end: ${endTime}`;\r\n\t\t}\r\n\t}\r\n\r\n\tonTimeChange()\r\n\t{\r\n\t\tif (this.finished) {\r\n\t\t\tif (this.props.timer) this.props.timer.stop();\r\n\t\t\tthis.props.onFinish();\r\n\t\t}\r\n\t\telse if (this.props.onTick) { this.props.onTick(this.state.time, this.state.ticker); }\r\n\t}\r\n\r\n\tcomponentDidUpdate(prevProps, prevState)\r\n\t{\r\n\t\tif (prevProps.paused && !this.props.paused) {\r\n\t\t\tthis.timerStartDt = this.getDateTime();\r\n\t\t\tthis.setState({resumeTime: this.state.time});\r\n\t\t}\r\n\r\n\t\tthis.finished = this.isFinished();\r\n\t\tthis.checkTime(prevState.time);\r\n\t\tthis.tryUpdateTime(prevProps.timer.tick);\r\n\t}\r\n\r\n\tcheckTime(oldTime)\r\n\t{\r\n\t\tconst newTime = this.state.time;\r\n\r\n\t\tif (oldTime !== newTime) {\r\n\t\t\tthis.onTimeChange();\r\n\t\t}\r\n\t}\r\n\r\n\ttryUpdateTime(oldTickValue)\r\n\t{\r\n\t\tif (this.props.paused) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst newTickValue = this.props.timer.tick;\r\n\t\tif (oldTickValue !== newTickValue) \r\n\t\t{\r\n\t\t\t// temporary solution for bypassing bug in 'react-timer-hoc'\r\n\t\t\t// see: https://github.com/troch/react-timer-hoc/issues/4\t\r\n\t\t\tif (newTickValue % 2 === 1)\r\n\t\t\t{\r\n\t\t\t\tthis.updateTime();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tcheckTimeDifference(currentDateTime){\r\n\t\tconst {showDebug}=this.props;\r\n\r\n const {resumeTime, direction} = this.state;\r\n let elapsedDateTime = getElapsedDateTime(this.timerStartDt, currentDateTime);\r\n let newTime = resumeTime + (direction * elapsedDateTime);\r\n \r\n const timeDifference = this.lastDateTime ? currentDateTime - this.lastDateTime:0;\r\n\r\n\r\n if (timeDifference<0 || (timeDifference>60*1000 && !this.hasLeftExam(currentDateTime, timeDifference))){\r\n\t\t\t\r\n\t\t\tconst previousTimerStartDt=parseInt(this.timerStartDt);\r\n\t\t\tconst description =\r\n timeDifference < 0\r\n ? \"System clock moved back so moving exam start date back\"\r\n : \"System clock moved forward so moving exam start date forward\";\r\n\r\n\t\t\tif (showDebug){\r\n\t\t\t\t//console.clear();\r\n\t\t\t\tconsole.log('Elapsed Time', elapsedDateTime, displayTime(elapsedDateTime));\r\n\t\t\t\tconsole.log('New Time', newTime, displayTime(newTime));\r\n\t\t\t\tconsole.log('Start Time', this.timerStartDt, displayDateTime(this.timerStartDt));\r\n\t\t\t\tconsole.log('Current Time', currentDateTime, displayDateTime(currentDateTime));\r\n\t\t\t\tconsole.log('Last Time', this.lastDateTime, displayDateTime(this.lastDateTime));\r\n\t\t\t\tconsole.log('Time Difference', timeDifference, displayTime(Math.abs(timeDifference)));\r\n\t\t\t}\r\n \r\n this.timerStartDt+=timeDifference;\r\n let elapsedDateTime = getElapsedDateTime(this.timerStartDt, currentDateTime);\r\n let newTime = resumeTime + (direction * elapsedDateTime);\r\n\r\n\t\t\tif (showDebug){\r\n\t\t\t\tconsole.log(description);\r\n\t\t\t\tconsole.log('New Start Time', this.timerStartDt, displayDateTime(this.timerStartDt));\r\n\t\t\t\tconsole.log('New Elapsed Time', elapsedDateTime, displayTime(elapsedDateTime));\r\n\t\t\t\tconsole.log('New Time', elapsedDateTime, displayTime(newTime)); \r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tconst log = {\r\n\t\t\t\tlastDateTime: this.lastDateTime,\r\n\t\t\t\tcurrentDateTime,\r\n\t\t\t\ttimeDifference,\r\n\t\t\t\tpreviousTimerStartDt,\r\n\t\t\t\tnewTimerStartDt: this.timerStartDt,\r\n\t\t\t\tdescription\r\n\t\t\t};\r\n\t\t\tif (showDebug){\r\n\t\t\t\tconsole.log('');\r\n\t\t\t\tconsole.log('Log', log);\r\n\t\t\t}\r\n\r\n\t\t\tactivityLogger.log(ACTIVITIES.CHANGE_SYSTEM_TIME, log);\r\n }\r\n else if (showDebug){\r\n this.debugTimerCounter++;\r\n\r\n if (this.debugTimerCounter % 60 === 0) {\r\n console.log('');\r\n console.log('Current Time', currentDateTime, displayDateTime(currentDateTime));\r\n console.log('New Elapsed Time', elapsedDateTime, displayTime(elapsedDateTime));\r\n console.log('New Time', newTime, displayTime(newTime));\r\n\t\t\t\tconsole.log('Time Difference', timeDifference, displayTime(Math.abs(timeDifference)));\r\n }\r\n }\r\n\r\n this.lastDateTime = currentDateTime;\r\n }\r\n\r\n\thasLeftExam(currentDateTime, timeDifference) {\r\n\t\tconst { showDebug } = this.props;\r\n\t\tif (showDebug){\r\n\t\t\tconsole.log('debugTimerCounter', this.debugTimerCounter);\r\n\t\t\tconsole.log(\r\n \"Checking to see if left exam for time difference:\",\r\n timeDifference,\r\n displayTime(Math.abs(timeDifference)),\r\n displayDateTime(currentDateTime)\r\n );\t\r\n\t\t}\r\n const { leaveExam, resumeExam } = this.props;\r\n\r\n if (!leaveExam) {\r\n if (showDebug) {\r\n console.log(\"Has not left app\");\r\n }\r\n return false;\r\n } else if (!resumeExam || leaveExam > resumeExam) {\r\n if (showDebug) {\r\n console.log(\"Has left exam but has not yet resumed\");\r\n }\r\n return true;\r\n }\r\n\r\n const diff = currentDateTime - resumeExam;\r\n if (showDebug) {\r\n console.log(\r\n \"Has resumed exam and diff from current time is \",\r\n diff\r\n );\r\n }\r\n return diff < 1000;\r\n }\r\n\r\n\tupdateTime()\r\n\t{\r\n\t\tconst {time, resumeTime, direction} = this.state;\r\n\t\tconst currentDateTime = this.getDateTime();\r\n\r\n\t\tthis.checkTimeDifference(currentDateTime);\r\n\r\n\t\tconst elapsedDateTime = getElapsedDateTime(this.timerStartDt, currentDateTime);\r\n\t\tconst newTime = resumeTime + (direction * elapsedDateTime);\r\n\t\tconst ticker = this.lastDateTime ? getElapsedDateTime(this.lastDateTime, currentDateTime):getElapsedDateTime(this.timerStartDt, currentDateTime);\r\n\t\t// console.log('Ticker', ticker);\r\n\t\t// console.log({\r\n\t\t// \tresumeTime,\r\n\t\t// \ttimerStartDt: this.timerStartDt,\r\n\t\t// \tcurrentDt: currentDateTime,\r\n\t\t// \telapsed: elapsedDateTime,\r\n\t\t// \toldTime: time,\r\n\t\t// \tnewTime\r\n\t\t// });\r\n\t\tthis.lastDateTime = parseInt(currentDateTime, 10);\r\n\r\n\t\tthis.setState({time: newTime, ticker});\r\n\t}\r\n\r\n\tgetDateTime()\r\n\t{\r\n\t\treturn Date.now();\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tif (this.finished) { return null; }\r\n\r\n\t\tconst uiProps = {\r\n\t\t\ttime: this.state.time, \r\n\t\t\tendTime: this.state.endTime,\r\n\t\t\tstartTime: this.state.startTime\r\n\t\t}\r\n\r\n\t\treturn React.cloneElement(this.props.children, uiProps);\r\n\t}\r\n\r\n\tisFinished()\r\n\t{\r\n\t\tconst {direction, time, endTime} = this.state;\r\n\t\treturn direction === 1 ? (time >= endTime) : (time <= endTime);\r\n\t}\r\n}\r\n\r\nTimer.propTypes = {\r\n\tchildren: PropTypes.node.isRequired,\r\n\tresumeTime: PropTypes.number,\t\t\t\t\t\t\t// in ms\r\n\tstartTime: PropTypes.number.isRequired,\t\t// in ms\r\n\tendTime: PropTypes.number.isRequired, \t\t// in ms\r\n\ttickTime: PropTypes.number.isRequired, \t\t// in ms\r\n\tonFinish: PropTypes.func.isRequired,\r\n\tdirection: PropTypes.oneOf([-1, 1]).isRequired,\r\n\tonTick: PropTypes.func,\r\n\tpaused: PropTypes.bool,\r\n\r\n\t// timer hoc props\r\n\ttimer: PropTypes.object.isRequired\r\n}\r\n\r\nTimer.defaultProps = {\r\n\tdirection: -1, // 1 for stopwatch\r\n\tstartTime: 10000,\r\n\tendTime: 0,\r\n\ttickTime: 1000\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n\r\n return {\r\n leaveExam: getLeaveExam(examData),\r\n resumeExam: getResumeExam(examData),\r\n\t\tshowDebug: false,\r\n };\r\n};\r\n\r\nTimer = connect(mapStoreToProps)(Timer);\r\n\r\n\r\n// Timer (connected to reactTimer)\r\n// -------------------------------------------------------\r\n// the delay will change based on the incoming 'tickTime' prop)\r\nTimer = reactTimer(1000)(Timer); \r\n\r\n\r\n// Export\r\n// -------------------------------------------------------\r\nexport {Timer}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport partialCircle from 'svg-partial-circle'\r\nimport {milliseconds, seconds, minutes} from 'time-convert'\r\n\r\n\r\n// constants\r\nconst MAX_ANGLE = 2 * Math.PI;\r\nconst SIZE = 64;\r\nconst CENTER = SIZE / 2;\r\nconst RADIUS = Math.round(CENTER / 4 * 3);\r\nconst ONE_MIN = minutes.to(milliseconds)(1);\r\nconst ANGLE_OFFSET = Math.PI / 2;\r\n\r\n\r\nclass TimerUi extends React.Component\r\n{\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.Circle}\r\n\t\t\t\t{this.Text}\r\n\t\t\t\t{this.ElapsedArc}\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tget Circle()\r\n\t{\r\n\t\tconst dimensionalProps = {cx: CENTER, cy: CENTER, r: RADIUS};\r\n\t\tconst colorProps = {stroke: 'gray', fill: this.props.color};\r\n\t\treturn ;\r\n\t}\r\n\r\n\tget Text()\r\n\t{\r\n\t\tconst {time, textColor} = this.props;\r\n\t\tconst textValue = time > ONE_MIN ?\r\n\t\t\t`${Math.ceil(minutes.from(milliseconds)(time))}m` : \r\n\t\t\t`${Math.ceil(seconds.from(milliseconds)(time))}s`;\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{textValue}\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tget ElapsedArc()\r\n\t{\r\n\t\tconst [startAngle, endAngle] = this.calculateElapsedArcAngles();\r\n\t\tconst commands = partialCircle(CENTER, CENTER, RADIUS, startAngle, endAngle);\r\n\t\tif (commands.length === 0) { return null; }\r\n\r\n\t\tconst arcCurveCommands = commands[1];\r\n\t\tconst arcEndPoint = {x: arcCurveCommands[6], y: arcCurveCommands[7]};\r\n\t\tconst dPathAttribute = commands.map(command => command.join(' ')).join(' ');\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t,\r\n\t\t\t\t \r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tcalculateElapsedArcAngles()\r\n\t{\r\n\t\tconst {time, startTime, endTime} = this.props;\r\n\t\tconst toElapse = Math.abs(endTime - startTime);\r\n\t\tconst elapsed = Math.abs(time - startTime);\r\n\t\tconst endAngle = (elapsed / toElapse) * MAX_ANGLE;\r\n\t\treturn [-ANGLE_OFFSET, endAngle - ANGLE_OFFSET];\r\n\t}\r\n}\r\n\r\nTimerUi.propTypes = {\r\n\ttime: PropTypes.number.isRequired,\r\n\tendTime: PropTypes.number.isRequired,\r\n\tstartTime: PropTypes.number.isRequired,\r\n\ttextColor: PropTypes.string.isRequired,\r\n\tcolor: PropTypes.string.isRequired\r\n}\r\n\r\nTimerUi.defaultProps = {\r\n\ttextColor: 'black',\r\n\tcolor: 'white'\r\n}\r\n\r\n\r\nexport {TimerUi}","\r\nimport check from 'check-types'\r\n\r\nimport * as SELECTORS from './selectors'\r\nimport * as TIME_SELECTORS from './time/selectors'\r\nimport * as CONTENT_COMPLEX_SELECTORS from './content/complex-selectors'\r\n\r\n\r\n\r\nconst getReadingTimeRemaining = (examData, questionId) =>\r\n{\r\n\tconst contentData = SELECTORS.getContentData(examData);\r\n\tconst readingTimeRemaining = CONTENT_COMPLEX_SELECTORS.getReadingTimeRemaining(contentData, questionId);\r\n\tif (check.assigned(readingTimeRemaining)) { return readingTimeRemaining; }\r\n\r\n\tconst timeData = SELECTORS.getTimeData(examData);\r\n\treturn readingTimeRemaining || TIME_SELECTORS.getReadingTimeRemaining(timeData) || 0;\r\n}\r\n\r\nconst getAnsweringTimeRemaining = (examData, questionId) =>\r\n{\r\n\tconst contentData = SELECTORS.getContentData(examData);\r\n\tconst answeringTimeRemaining = CONTENT_COMPLEX_SELECTORS.getAnsweringTimeRemaining(contentData, questionId);\r\n\tif (check.assigned(answeringTimeRemaining)) { return answeringTimeRemaining; }\r\n\r\n\tconst timeData = SELECTORS.getTimeData(examData);\r\n\treturn answeringTimeRemaining || TIME_SELECTORS.getExamTimeRemaining(timeData) || 0;\r\n}\r\n\r\nconst getTimeRemaining = (examData, questionId) =>\r\n{\r\n\tconst readingTimeRemaining = getReadingTimeRemaining(examData, questionId);\r\n\tconst answeringTimeRemaining = getAnsweringTimeRemaining(examData, questionId);\r\n\treturn readingTimeRemaining + answeringTimeRemaining;\r\n}\r\n\r\nconst getReadingTime = (examData, questionId) =>\r\n{\r\n\tconst contentData = SELECTORS.getContentData(examData);\r\n\tconst readingTimeRemaining = CONTENT_COMPLEX_SELECTORS.getReadingTime(contentData, questionId);\r\n\tif (check.assigned(readingTimeRemaining)) { return readingTimeRemaining; }\r\n\r\n\tconst timeData = SELECTORS.getTimeData(examData);\r\n\treturn readingTimeRemaining || TIME_SELECTORS.getReadingTime(timeData) || 0;\r\n}\r\n\r\nconst getAnsweringTime = (examData, questionId) =>\r\n{\r\n\tconst contentData = SELECTORS.getContentData(examData);\r\n\tconst readingTimeRemaining = CONTENT_COMPLEX_SELECTORS.getAnsweringTime(contentData, questionId);\r\n\tif (check.assigned(readingTimeRemaining)) { return readingTimeRemaining; }\r\n\r\n\tconst timeData = SELECTORS.getTimeData(examData);\r\n\treturn readingTimeRemaining || TIME_SELECTORS.getExamTime(timeData) || 0;\r\n}\r\n\r\nconst getWarningTime = (examData, questionId) =>\r\n{\r\n\tconst contentData = SELECTORS.getContentData(examData);\r\n\tconst readingTimeRemaining = CONTENT_COMPLEX_SELECTORS.getWarningTime(contentData, questionId);\r\n\tif (check.assigned(readingTimeRemaining)) { return readingTimeRemaining; }\r\n\r\n\tconst timeData = SELECTORS.getTimeData(examData);\r\n\treturn readingTimeRemaining || TIME_SELECTORS.getWarningTime(timeData) || 0;\r\n}\r\n\r\n\r\nexport {\r\n\tgetReadingTimeRemaining,\r\n\tgetAnsweringTimeRemaining,\r\n\tgetTimeRemaining,\r\n\tgetReadingTime,\r\n\tgetAnsweringTime,\r\n\tgetWarningTime\r\n}","\r\n// npm\r\nimport React, { Component } from 'react';\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// react\r\nimport {Timer} from 'components/functional/timer'\r\nimport {TimerUi} from 'components/presentation/timer'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getPaperPartId} from 'redux/reducers/exam/content/complex-selectors'\r\nimport {getSectionDataById} from 'redux/reducers/exam/content/sections/selectors'\r\nimport {getContentData, getTimeData, isExamPaused} from 'redux/reducers/exam/selectors'\r\nimport {getSectionsData, getCurrentQuestionId} from 'redux/reducers/exam/content/selectors'\r\nimport {getReadingTimeRemaining, getAnsweringTimeRemaining, getReadingTime, getAnsweringTime, getWarningTime} from 'redux/reducers/exam/complex-selectors'\r\n\r\n// redux (actions)\r\nimport {setExamTimeRemaining, setSectionTimeRemaining, setExamReadingTimeRemaining, setSectionReadingTimeRemaining, incrementExamElapsedTime} from './actions'\r\n\r\nimport { activityLogger, ACTIVITIES } from 'libs/activity_logger/activity-logger'\r\n\r\n// ExamTimer (not connected to store)\r\n// ------------------------------------------------\r\n\r\nclass ExamTimer extends Component {\r\n constructor(props){\r\n super(props);\r\n\r\n this.handleOnTick = this.handleOnTick.bind(this);\r\n }\r\n\r\n componentDidMount(){\r\n const {startTime, resumeTime, timerKey, timerPaperPartId} = this.props;\r\n\r\n activityLogger.log(ACTIVITIES.EXAM_TIMER, {\r\n mode: 'mount',\r\n startTime,\r\n resumeTime,\r\n timerPaperPartId,\r\n timerKey\r\n });\r\n }\r\n\r\n componentDidUpdate(prevProps){\r\n const {startTime: prevStartTime, resumeTime:prevResumeTime} = prevProps;\r\n const {startTime, resumeTime, timerKey, timerPaperPartId} = this.props;\r\n\r\n if (startTime !== prevStartTime) {\r\n activityLogger.log(ACTIVITIES.EXAM_TIMER, {\r\n mode: 'update',\r\n startTime,\r\n resumeTime,\r\n timerPaperPartId,\r\n timerKey,\r\n prevStartTime,\r\n prevResumeTime\r\n }); \r\n }\r\n }\r\n\r\n handleOnTick(value, tickValue){\r\n const {setTimeRemaining, incrementElapsedTime} = this.props;\r\n\r\n setTimeRemaining(value);\r\n if (tickValue) incrementElapsedTime(tickValue)\r\n }\r\n\r\n render() { \r\n const {startTime, resumeTime, warningTime, timerPaperPartId, onFinish, timerKey, setTimeRemaining, paused} = this.props;\r\n\r\n const timerProps = {\r\n key: timerKey,\r\n startTime: startTime,\r\n resumeTime: resumeTime,\r\n onTick: this.handleOnTick,\r\n paused: paused,\r\n onFinish: ()=>{onFinish(timerPaperPartId)}\r\n }\r\n \r\n const TimerUiComponent = (props) => {\r\n const color = resumeTime <= warningTime ? 'orange' : undefined;\r\n return ;\r\n }\r\n \r\n return ;\r\n }\r\n}\r\n\r\nExamTimer.propTypes = {\r\n\ttimerKey: PropTypes.oneOfType([\r\n\t\tPropTypes.number,\r\n\t\tPropTypes.string\r\n\t]).isRequired,\r\n\tonFinish: PropTypes.func.isRequired,\r\n\tstartTime: PropTypes.number.isRequired,\r\n\twarningTime: PropTypes.number.isRequired,\r\n\tresumeTime: PropTypes.number.isRequired,\r\n\tsetTimeRemaining: PropTypes.func.isRequired,\r\n\tpaused: PropTypes.bool.isRequired\r\n}\r\n\r\n\r\n// ExamTimer (connected to store)\r\n// ------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({store});\r\nconst mapDispatchToProps = (dispatch) => ({dispatch});\r\n\r\nconst mergeProps = ({store}, {dispatch}, {reading, onFinish, currentQuestionId, timerPaperPartId}) =>\r\n{\r\n\tconst examData = getExamData(store);\r\n\tconst contentData = getContentData(examData);\r\n\tconst paperPartId = getPaperPartId(contentData)(currentQuestionId);\r\n\r\n\tconst startTime = reading ? getReadingTime(examData, currentQuestionId) : getAnsweringTime(examData, currentQuestionId);\r\n\tconst warningTime = reading ? 0 : getWarningTime(examData, currentQuestionId);\r\n\tconst resumeTime = reading ? getReadingTimeRemaining(examData, currentQuestionId) : getAnsweringTimeRemaining(examData, currentQuestionId);\r\n\r\n\tconst setTimeRemaining = paperPartId ?\r\n\t\t(value) => dispatch(reading ? setSectionReadingTimeRemaining(value, paperPartId) : setSectionTimeRemaining(value, paperPartId)) :\r\n\t\t(value) => dispatch(reading ? setExamReadingTimeRemaining(value) : setExamTimeRemaining(value));\r\n\r\n const incrementElapsedTime = (value) =>{\r\n dispatch(incrementExamElapsedTime(value));\r\n }\r\n\r\n\r\n\tconst timerKey = `exam-timer${paperPartId}${reading}`;\r\n\tconst paused = isExamPaused(getExamData(store));\r\n\r\n\treturn {onFinish, startTime, warningTime, resumeTime, setTimeRemaining, timerKey, paused, timerPaperPartId, incrementElapsedTime};\r\n}\r\n\r\n\r\nconst args = [mapStoreToProps, mapDispatchToProps, mergeProps];\r\nExamTimer = connect(...args)(ExamTimer);\r\n\r\nExamTimer.propTypes = {\r\n\treading: PropTypes.bool.isRequired\r\n}\r\n\r\n\r\n// Export\r\n// ------------------------------------------------\r\nexport {ExamTimer}","\r\n// npm\r\nimport {milliseconds, seconds} from 'time-convert'\r\n\r\n// redux (action-types)\r\nimport {SET_CURRENT_QUESTION_ID} from 'redux/reducers/exam/content/action-types'\r\n\r\nimport {\r\n\tSET_EXAM_TIME_REMAINING,\r\n\tSET_READING_TIME_REMAINING as SET_EXAM_READING_TIME_REMAINING,\r\n\tINCREMENT_EXAM_ELAPSED_TIME\t\r\n} from 'redux/reducers/exam/time/action-types'\r\n\r\nimport {\r\n\tSET_COMPLETE,\r\n\tSET_TIME_REMAINING as SET_SECTION_TIME_REMAINING,\r\n\tSET_READING_TIME_REMAINING as SET_SECTION_READING_TIME_REMAINING,\r\n} from 'redux/reducers/exam/content/sections/section/action-types'\r\n\r\n\r\nconst setCurrentQuestionId = (value) => ({\r\n\ttype: SET_CURRENT_QUESTION_ID,\r\n\tvalue\r\n});\r\n\r\nconst setPaperPartComplete = (id) => ({\r\n\ttype: SET_COMPLETE,\r\n\tid\r\n})\r\n\r\n\r\nconst setExamTimeRemaining = (value) => ({\r\n\ttype: SET_EXAM_TIME_REMAINING,\r\n\tvalue: seconds.from(milliseconds)(value)\r\n});\r\n\r\nconst setExamReadingTimeRemaining = (value) => ({\r\n\ttype: SET_EXAM_READING_TIME_REMAINING,\r\n\tvalue: seconds.from(milliseconds)(value)\r\n});\r\n\r\nconst setSectionTimeRemaining = (value, id) => ({\r\n\ttype: SET_SECTION_TIME_REMAINING,\r\n\tvalue: seconds.from(milliseconds)(value),\r\n\tid\r\n});\r\n\r\nconst setSectionReadingTimeRemaining = (value, id) => ({\r\n\ttype: SET_SECTION_READING_TIME_REMAINING,\r\n\tvalue: seconds.from(milliseconds)(value),\r\n\tid\r\n});\r\n\r\nconst incrementExamElapsedTime = (value)=>({\r\n\ttype: INCREMENT_EXAM_ELAPSED_TIME,\r\n\tvalue: value,\t\r\n})\r\n\r\n\r\nexport {\r\n\tsetCurrentQuestionId,\r\n\tsetPaperPartComplete,\r\n\tsetExamTimeRemaining,\r\n\tsetSectionTimeRemaining,\r\n\tsetExamReadingTimeRemaining,\r\n\tsetSectionReadingTimeRemaining,\r\n\tincrementExamElapsedTime\r\n}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react (layout)\r\nimport {ExamLayout} from 'components/pages/exam/_layout/layout'\r\nimport {Align} from 'components/layout/align'\r\n\r\n// react (concrete)\r\nimport {ResumeButton} from './resume-button'\r\nimport {InstructionsTitle} from './instructions-title'\r\nimport {ExamTimer} from 'components/pages/exam/running/exam-timer'\r\nimport {InstructionsPanel} from 'components/pages/exam/running/instructions-panel'\r\n\r\n\r\nlet ExamInstructionsPage = (props) =>\r\n{\r\n\tconst examLayoutProps = {\r\n\t\tbar: {\r\n\t\t\tcontent: (\r\n\t\t\t\t\r\n\t\t\t\t\t \r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t \r\n\t\t\t)\r\n\t\t},\r\n\t\tcentre: {\r\n\t\t\tcontent: (\r\n\t\t\t\t\r\n\t\t\t\t\t \r\n\t\t\t\t \r\n\t\t\t)\r\n\t\t}\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nExamInstructionsPage.propTypes = {\r\n\tonShowQuestions: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nexport {ExamInstructionsPage}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Button from '@material-ui/core/Button'\r\n\r\n\r\nconst PopupButton = (props) =>\r\n{\r\n\tconst buttonProps = {\r\n\t\tsize: 'small',\r\n\t\tcolor: props.color,\r\n\t\tvariant: 'contained',\r\n\t\tdisableRipple: true,\r\n\t\tonClick: props.onClick,\r\n\t\tdisableFocusRipple: true,\r\n\t\tstyle: {\r\n\t\t\t...(props.style || {}),\r\n\t\t\tmarginLeft: 4,\r\n\t\t\tmarginRight: 4\r\n\t\t}\r\n\t}\r\n\r\n\treturn {props.text} ;\r\n}\r\n\r\nPopupButton.propTypes = {\r\n\tcolor: PropTypes.string.isRequired,\r\n\tonClick: PropTypes.func.isRequired,\r\n\ttext: PropTypes.string.isRequired\r\n}\r\n\r\nPopupButton.defaultProps = {\r\n\tcolor: 'primary'\r\n}\r\n\r\n\r\nexport {PopupButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {PopupButton} from './popup-button'\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst confirmMessageId = MESSAGE_IDS.GENERAL.CONFIRM;\r\n\r\n\r\nlet ConfirmButton = ({onClick, messages}) =>\r\n{\r\n\tconst text = messages[confirmMessageId];\r\n\treturn ;\r\n}\r\n\r\nConfirmButton.propTypes = {\r\n\tonClick: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[confirmMessageId]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nConfirmButton = withMessages(ConfirmButton);\r\n\r\n\r\nexport {ConfirmButton}","\r\n// utils\r\nimport {massReplace} from 'custom/string-helper'\r\n\r\n\r\nconst detokenizeMessage = (message, tokenMappings={}) =>\r\n{\r\n\tObject.keys(tokenMappings).forEach(token => {\r\n\t\tconst replacement = tokenMappings[token];\r\n\t\tmessage = massReplace(message, token, replacement);\r\n\t});\r\n\r\n\treturn message;\r\n}\r\n\r\n\r\nexport {detokenizeMessage}","\r\nconst MESSAGE_TOKENS = {\r\n\tCURRENT_PAPER_PART: \"¬PaperPart¬\",\r\n\tNEXT_PAPER_PART: \"¬NextPaperPart¬\"\r\n}\r\n\r\n\r\nObject.keys(MESSAGE_TOKENS).forEach((key) => {\r\n\twhile (MESSAGE_TOKENS[key].indexOf('¬') >= 0) {\r\n\t\tMESSAGE_TOKENS[key] = MESSAGE_TOKENS[key].replace(\"¬\", \"¬\"); // the server sends down \"¬\" instead of \"¬\"\r\n\t}\r\n});\r\n\r\n\r\nexport {MESSAGE_TOKENS}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {ConfirmButton} from './buttons/confirm'\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// utils\r\nimport {detokenizeMessage} from 'utils/detokenize-message'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\nimport {MESSAGE_TOKENS} from 'constants/message-tokens'\r\n\r\n\r\nconst finishPartTitle = MESSAGE_IDS.EXAM.FINISH_PAPER_PART_ALERT_TITLE;\r\nconst finishPartText = MESSAGE_IDS.EXAM.FINISH_PAPER_PART_ALERT_INFO;\r\n\r\n\r\nlet PartFinishedPopup = ({onConfirm, messages, data}) =>\r\n{\r\n\tconst tokenMappings = {\r\n\t\t[MESSAGE_TOKENS.CURRENT_PAPER_PART]: data.paperPartName,\r\n\t\t[MESSAGE_TOKENS.NEXT_PAPER_PART]: data.nextPaperPartName\r\n\t}\r\n\r\n\tconst popupTitle = messages[finishPartTitle];\r\n\tconst popupText = messages[finishPartText];\r\n\r\n\tconst props = {\r\n\t\ttitle: detokenizeMessage(popupTitle, tokenMappings),\r\n\t\tcontent: {text: detokenizeMessage(popupText, tokenMappings)},\r\n\t\tbuttons: []\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nPartFinishedPopup.propTypes = {\r\n\tonClose: PropTypes.func.isRequired,\r\n\tonConfirm: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[finishPartTitle]: PropTypes.string.isRequired,\r\n\t\t[finishPartText]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nPartFinishedPopup = withMessages(PartFinishedPopup);\r\n\r\n\r\nexport {PartFinishedPopup}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {PopupButton} from './popup-button'\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst cancelMessageId = MESSAGE_IDS.GENERAL.CANCEL;\r\n\r\n\r\nlet CancelButton = ({onClick, messages}) =>\r\n{\r\n\tconst text = messages[cancelMessageId];\r\n\treturn ;\r\n}\r\n\r\nCancelButton.propTypes = {\r\n\tonClick: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[cancelMessageId]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nCancelButton = withMessages(CancelButton);\r\n\r\n\r\nexport {CancelButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {CancelButton} from './buttons/cancel'\r\nimport {ConfirmButton} from './buttons/confirm'\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst finishConfirmTitle1 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_TITLE_1;\r\nconst finishConfirm1 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_INFO_1;\r\n\r\n\r\nlet ExamFinishConfirmPopup = ({onClose, onConfirm, messages}) =>\r\n{\r\n\tconst props = {\r\n\t\ttitle: messages[finishConfirmTitle1],\r\n\t\tcontent: {text: messages[finishConfirm1]},\r\n\t\tbuttons: [\r\n\t\t\t,\r\n\t\t\t\r\n\t\t],\r\n\t\tonClose\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nExamFinishConfirmPopup.propTypes = {\r\n\tonClose: PropTypes.func.isRequired,\r\n\tonConfirm: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[finishConfirmTitle1]: PropTypes.string.isRequired,\r\n\t\t[finishConfirm1]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nExamFinishConfirmPopup = withMessages(ExamFinishConfirmPopup);\r\n\r\n\r\nexport {ExamFinishConfirmPopup}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {CancelButton} from './buttons/cancel'\r\nimport {ConfirmButton} from './buttons/confirm'\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst finishConfirmTitle2 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_TITLE_2;\r\nconst finishConfirm2 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_INFO_2;\r\n\r\n\r\nlet ExamFinishConfirmPopup2 = ({onClose, onConfirm, messages}) =>\r\n{\r\n\tconst props = {\r\n\t\ttitle: messages[finishConfirmTitle2],\r\n\t\tcontent: {text: messages[finishConfirm2]},\r\n\t\tbuttons: [\r\n\t\t\t,\r\n\t\t\t\r\n\t\t],\r\n\t\tonClose\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nExamFinishConfirmPopup2.propTypes = {\r\n\tonClose: PropTypes.func.isRequired,\r\n\tonConfirm: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[finishConfirmTitle2]: PropTypes.string.isRequired,\r\n\t\t[finishConfirm2]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nExamFinishConfirmPopup2 = withMessages(ExamFinishConfirmPopup2);\r\n\r\n\r\nexport {ExamFinishConfirmPopup2}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {CancelButton} from './buttons/cancel'\r\nimport {ConfirmButton} from './buttons/confirm'\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// utils\r\nimport {detokenizeMessage} from 'utils/detokenize-message'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\nimport {MESSAGE_TOKENS} from 'constants/message-tokens'\r\n\r\n\r\nconst finishConfirmPartTitle1 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_PART_TITLE_1;\r\nconst finishConfirmPart1 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_PART_INFO_1;\r\n\r\n\r\nlet PartFinishConfirmPopup = ({onClose, onConfirm, messages, data}) =>\r\n{\r\n\tconst tokenMappings = {\r\n\t\t[MESSAGE_TOKENS.CURRENT_PAPER_PART]: data.paperPartName,\r\n\t\t[MESSAGE_TOKENS.NEXT_PAPER_PART]: data.nextPaperPartName\r\n\t}\r\n\r\n\tconst popupTitle = messages[finishConfirmPartTitle1];\r\n\tconst popupText = messages[finishConfirmPart1];\r\n\r\n\tconst props = {\r\n\t\ttitle: detokenizeMessage(popupTitle, tokenMappings),\r\n\t\tcontent: {text: detokenizeMessage(popupText, tokenMappings)},\r\n\t\tbuttons: [\r\n\t\t\t,\r\n\t\t\t\r\n\t\t],\r\n\t\tonClose\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nPartFinishConfirmPopup.propTypes = {\r\n\tonClose: PropTypes.func.isRequired,\r\n\tonConfirm: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[finishConfirmPartTitle1]: PropTypes.string.isRequired,\r\n\t\t[finishConfirmPart1]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nPartFinishConfirmPopup = withMessages(PartFinishConfirmPopup);\r\n\r\n\r\nexport {PartFinishConfirmPopup}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {CancelButton} from './buttons/cancel'\r\nimport {ConfirmButton} from './buttons/confirm'\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// utils\r\nimport {detokenizeMessage} from 'utils/detokenize-message'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\nimport {MESSAGE_TOKENS} from 'constants/message-tokens'\r\n\r\n\r\nconst finishConfirmPartTitle2 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_PART_TITLE_2;\r\nconst finishConfirmPart2 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_PART_INFO_2;\r\n\r\n\r\nlet PartFinishConfirmPopup2 = ({onClose, onConfirm, messages, data}) =>\r\n{\r\n\tconst tokenMappings = {\r\n\t\t[MESSAGE_TOKENS.CURRENT_PAPER_PART]: data.paperPartName,\r\n\t\t[MESSAGE_TOKENS.NEXT_PAPER_PART]: data.nextPaperPartName\r\n\t}\r\n\r\n\tconst popupTitle = messages[finishConfirmPartTitle2];\r\n\tconst popupText = messages[finishConfirmPart2];\r\n\r\n\tconst props = {\r\n\t\ttitle: detokenizeMessage(popupTitle, tokenMappings),\r\n\t\tcontent: {text: detokenizeMessage(popupText, tokenMappings)},\r\n\t\tbuttons: [\r\n\t\t\t,\r\n\t\t\t\r\n\t\t],\r\n\t\tonClose\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nPartFinishConfirmPopup2.propTypes = {\r\n\tonClose: PropTypes.func.isRequired,\r\n\tonConfirm: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[finishConfirmPartTitle2]: PropTypes.string.isRequired,\r\n\t\t[finishConfirmPart2]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nPartFinishConfirmPopup2 = withMessages(PartFinishConfirmPopup2);\r\n\r\n\r\nexport {PartFinishConfirmPopup2}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {CancelButton} from './buttons/cancel'\r\nimport {ConfirmButton} from './buttons/confirm'\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst practiceFinishConfirmTitle1 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_PRACTICE_TITLE_1;\r\nconst practiceFinishConfirm1 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_PRACTICE_INFO_1;\r\n\r\n\r\nlet PracticeFinishConfirmPopup = ({onClose, onConfirm, messages}) =>\r\n{\r\n\tconst props = {\r\n\t\ttitle: messages[practiceFinishConfirmTitle1],\r\n\t\tcontent: {text: messages[practiceFinishConfirm1]},\r\n\t\tbuttons: [\r\n\t\t\t,\r\n\t\t\t\r\n\t\t],\r\n\t\tonClose\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nPracticeFinishConfirmPopup.propTypes = {\r\n\tonClose: PropTypes.func.isRequired,\r\n\tonConfirm: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[practiceFinishConfirmTitle1]: PropTypes.string.isRequired,\r\n\t\t[practiceFinishConfirm1]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nPracticeFinishConfirmPopup = withMessages(PracticeFinishConfirmPopup);\r\n\r\n\r\nexport {PracticeFinishConfirmPopup}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {Popup} from 'components/layout/popup'\r\nimport {CancelButton} from './buttons/cancel'\r\nimport {ConfirmButton} from './buttons/confirm'\r\nimport {withMessages} from 'components/hocs/messages'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst practiceFinishConfirmTitle2 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_PRACTICE_TITLE_2;\r\nconst practiceFinishConfirm2 = MESSAGE_IDS.EXAM.FINISH_CONFIRM_PRACTICE_INFO_2;\r\n\r\n\r\nlet PracticeFinishConfirmPopup2 = ({onClose, onConfirm, messages}) =>\r\n{\r\n\tconst props = {\r\n\t\ttitle: messages[practiceFinishConfirmTitle2],\r\n\t\tcontent: {text: messages[practiceFinishConfirm2]},\r\n\t\tbuttons: [\r\n\t\t\t,\r\n\t\t\t\r\n\t\t],\r\n\t\tonClose\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nPracticeFinishConfirmPopup2.propTypes = {\r\n\tonClose: PropTypes.func.isRequired,\r\n\tonConfirm: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[practiceFinishConfirmTitle2]: PropTypes.string.isRequired,\r\n\t\t[practiceFinishConfirm2]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nPracticeFinishConfirmPopup2 = withMessages(PracticeFinishConfirmPopup2);\r\n\r\n\r\nexport {PracticeFinishConfirmPopup2}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {PartFinishedPopup} from './part-finished'\r\nimport {ExamFinishConfirmPopup} from './exam-finish-confirm'\r\nimport {ExamFinishConfirmPopup2} from './exam-finish-confirm-2'\r\nimport {PartFinishConfirmPopup} from './part-finish-confirm'\r\nimport {PartFinishConfirmPopup2} from './part-finish-confirm-2'\r\nimport {PracticeFinishConfirmPopup} from './practice-finish-confirm'\r\nimport {PracticeFinishConfirmPopup2} from './practice-finish-confirm-2'\r\n\r\n\r\nconst POPUP_TYPES = {\r\n EXAM_FINISH_CONFIRM: 0,\r\n EXAM_FINISH_CONFIRM_2: 1,\r\n PART_FINISH_CONFIRM: 2,\r\n PART_FINISH_CONFIRM_2: 3,\r\n PRACTICE_EXAM_FINISH_CONFIRM: 4,\r\n PRACTICE_EXAM_FINISH_CONFIRM_2: 5,\r\n PART_FINISHED: 6\r\n}\r\n\r\n\r\nconst extractPopupData = (type, data) =>\r\n{\r\n\tswitch (type)\r\n\t{\r\n\t\tcase POPUP_TYPES.PART_FINISH_CONFIRM:\r\n\t\tcase POPUP_TYPES.PART_FINISH_CONFIRM_2:\r\n\t\tcase POPUP_TYPES.PART_FINISHED:\r\n\t\t\treturn data;\r\n\t\tcase POPUP_TYPES.EXAM_FINISH_CONFIRM:\r\n\t\tcase POPUP_TYPES.EXAM_FINISH_CONFIRM_2:\r\n\t\tcase POPUP_TYPES.PRACTICE_EXAM_FINISH_CONFIRM:\r\n\t\tcase POPUP_TYPES.PRACTICE_EXAM_FINISH_CONFIRM_2:\r\n\t\tdefault:\r\n\t\t\treturn undefined;\r\n\t}\r\n}\r\n\r\nconst ExamPopup = ({type, data, ...props}) =>\r\n{\r\n\tconst popupProps = {data: extractPopupData(type, data), ...props};\r\n\r\n\tswitch (type)\r\n\t{\r\n\t\tcase POPUP_TYPES.EXAM_FINISH_CONFIRM:\r\n\t\t\treturn ;\r\n\t\tcase POPUP_TYPES.EXAM_FINISH_CONFIRM_2:\r\n\t\t\treturn ;\r\n\t\tcase POPUP_TYPES.PART_FINISH_CONFIRM:\r\n\t\t\treturn ;\r\n\t\tcase POPUP_TYPES.PART_FINISH_CONFIRM_2:\r\n\t\t\treturn ;\r\n\t\tcase POPUP_TYPES.PRACTICE_EXAM_FINISH_CONFIRM:\r\n\t\t\treturn ;\r\n\t\tcase POPUP_TYPES.PRACTICE_EXAM_FINISH_CONFIRM_2:\r\n\t\t\treturn ;\r\n\t\tcase POPUP_TYPES.PART_FINISHED:\r\n\t\t\treturn \r\n\t\tdefault:\r\n\t\t\treturn null;\r\n\t}\r\n}\r\n\r\nExamPopup.propTypes = {\r\n\ttype: PropTypes.oneOf(Object.values(POPUP_TYPES)),\r\n\tonConfirm: PropTypes.func.isRequired,\r\n\tonClose: PropTypes.func.isRequired,\r\n\tdata: PropTypes.shape({\r\n\t\tpaperPartName: PropTypes.string,\r\n\t\tnextPaperPartName: PropTypes.string\r\n\t}).isRequired,\r\n}\r\n\r\nExamPopup.defaultProps = {\r\n\ttype: null,\r\n\tdata: {}\r\n}\r\n\r\n\r\nexport {ExamPopup, POPUP_TYPES}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\n\r\n// material-ui\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\n// react\r\nimport { getAnswerBox } from \"./get-answer-box\";\r\n\r\n// redux (selectors)\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getContentData } from \"redux/reducers/exam/selectors\";\r\nimport { getQuestionsData } from \"redux/reducers/exam/content/selectors\";\r\nimport { getQuestionDataById } from \"redux/reducers/exam/content/questions/selectors\";\r\n\r\nimport * as QUESTION_TYPES from \"constants/question-types\";\r\n\r\nconst styles = (theme) => {\r\n const spacing = theme.spacing.unit * 2;\r\n return {\r\n panel: {\r\n margin: spacing,\r\n padding: spacing,\r\n backgroundColor: theme.palette.background.light,\r\n },\r\n };\r\n};\r\n\r\nconst hasAnswerBox = (questionData) => {\r\n const questionType = questionData.get(\"type\");\r\n\r\n return questionType !== QUESTION_TYPES.STATIC_TEXT;\r\n};\r\n\r\n// AnswerPanel (not connected to store/styles)\r\n// ------------------------------------------------------------------------\r\n\r\nlet AnswerPanel = ({ questionData, classes, noQuestionPanel }) => {\r\n return hasAnswerBox(questionData) && !noQuestionPanel ? (\r\n {getAnswerBox(questionData)} \r\n ) : (\r\n getAnswerBox(questionData, noQuestionPanel)\r\n );\r\n};\r\n\r\nAnswerPanel.propTypes = {\r\n questionData: PropTypes.object.isRequired,\r\n classes: PropTypes.object.isRequired,\r\n};\r\n\r\n// AnswerPanel (not connected to store/styles)\r\n// ------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store, { questionId }) => {\r\n const contentData = getContentData(getExamData(store));\r\n const questionsData = getQuestionsData(contentData);\r\n const questionData = getQuestionDataById(questionsData)(questionId);\r\n return { questionData, questionId: undefined };\r\n};\r\n\r\nAnswerPanel = connect(mapStoreToProps)(AnswerPanel);\r\nAnswerPanel = withStyles(styles)(AnswerPanel);\r\n\r\n// Export\r\n// ------------------------------------------------------------------------\r\nexport { AnswerPanel };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n\r\nconst ScenarioText = ({text, className}) =>\r\n{\r\n\tif (!text) { return null; }\r\n\treturn {text} ;\r\n}\r\n\r\nScenarioText.propTypes = {\r\n\tclassName: PropTypes.string,\r\n\ttext: PropTypes.string.isRequired\r\n}\r\n\r\n\r\nexport {ScenarioText}","\r\n// npm\r\nimport React from 'react'\r\nimport check from 'check-types'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// material-ui\r\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\nimport MuiExpansionPanel from '@material-ui/core/ExpansionPanel'\r\nimport ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'\r\nimport ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'\r\n\r\n\r\nconst getSummary = (summary, classes) =>\r\n{\r\n\tif (!check.string(summary)) { return summary; }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// summary as 'node'\r\n\treturn {summary} ;\t\t\t\t\t\t\t\t\t// summary as 'string'\r\n}\r\n\r\nlet ExpansionPanel = ({className, classes, children, startOpen, summary, open, onChange}) =>\r\n{\r\n\tconst panelProps = {className};\r\n\r\n\tif (check.assigned(open)) {\r\n\t\tpanelProps.expanded=open;\r\n\r\n\t\tif (check.assigned(onChange)) {\r\n\t\t\tpanelProps.onChange=onChange;\r\n\t\t}\r\n\t}\r\n\telse if (check.assigned(startOpen)) {\r\n\t\tpanelProps.defaultExpanded=startOpen;\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t}>\r\n\t\t\t\t{getSummary(summary, classes)}\r\n\t\t\t \r\n\t\t\t\r\n\t\t\t\t{children}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\nExpansionPanel.propTypes = {\r\n\tclassName: PropTypes.object,\r\n\tchildren: PropTypes.node.isRequired,\r\n\tstartOpen: PropTypes.bool,\r\n\tsummary: PropTypes.oneOfType([\r\n\t\tPropTypes.node,\r\n\t\tPropTypes.string\r\n\t])\r\n}\r\n\r\nExpansionPanel.defaultProps = {\r\n\tstartOpen: false,\r\n\tclassName: {},\r\n\tsummary: ''\r\n}\r\n\r\n\r\nconst styles = ({palette}) => ({\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText\r\n\t}\r\n})\r\n\r\nExpansionPanel = withStyles(styles)(ExpansionPanel);\r\n\r\n\r\nexport {ExpansionPanel}","// npm\r\nimport React, { Component } from 'react';\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {ScenarioText} from './scenario-text'\r\nimport {ExpansionPanel} from 'components/layout/expansion-panel'\r\n\r\n// redux (selectors)\r\nimport {getExamData, getSettingsData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getMappingsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getParentSectionId} from 'redux/reducers/exam/content/mappings/selectors'\r\nimport {getQuestionScenarioText, getQuestionNoInSection} from 'redux/reducers/exam/content/complex-selectors'\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getRememberScenario } from 'redux/reducers/settings/client/selectors';\r\n\r\nimport { getShowScenario } from \"redux/reducers/exam/selectors\";\r\nimport { SET_SHOW_SCENARIO } from \"redux/reducers/exam/action-types\";\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst styles = (theme) => ({\r\n\troot: {\r\n\t\tmargin: theme.spacing.unit * 2,\r\n\t\tbackgroundColor: theme.palette.background.light\r\n\t},\r\n\texpanded: {\r\n\t\tmargin: 0\r\n\t},\r\n\ttext: {\r\n\t\tcolor: theme.palette.background.contrastText\r\n\t}\r\n});\r\n\r\n\r\n// ScenarioPanel (not connected to store/styles)\r\n// ------------------------------------------------\r\n\r\nclass ScenarioPanel extends Component {\r\n\tstate = { }\r\n\r\n\tconstructor(props){\r\n\t\tsuper(props);\r\n\r\n\t\tthis.handleChange = this.handleChange.bind(this);\r\n\t}\r\n\r\n\thandleChange(e, value){\r\n\t\tconst {setShowScenario} = this.props;\r\n\t\tsetShowScenario(value);\r\n\t}\r\n\r\n\trender() { \r\n\t\tconst {text, classes, questionNumberInSection, messages, showScenario, rememberShowScenario} = this.props;\r\n\r\n\t\tif (!text) { return null; }\r\n\r\n\t\tconst props = {\r\n\t\t\tkey: `scenariotext${questionNumberInSection}`,\t\t\r\n\t\t\tsummary: messages[MESSAGE_IDS.EXAM.SCENARIO_HEADING],\r\n\t\t\tclasses: {expanded: classes.expanded},\r\n\t\t\tclassName: classes.root\r\n\t\t}\r\n\r\n\t\tif (rememberShowScenario){\r\n\t\t\tprops.open = showScenario;\r\n\t\t\tprops.onChange= this.handleChange;\r\n\t\t}\r\n\t\telse {\r\n\t\t\tprops.startOpen = questionNumberInSection === 1;\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t
\r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t \r\n\t\t\t \r\n\t\t)\t\t\r\n\t}\r\n}\r\n\r\nScenarioPanel.propTypes = {\r\n\ttext: PropTypes.string,\r\n\tclasses: PropTypes.object.isRequired,\r\n\tquestionNumberInSection: PropTypes.number.isRequired\r\n}\r\n\r\n\r\n// ScenarioPanel (connected to store/styles)\r\n// ------------------------------------------------\r\n\r\nconst mapStoreToProps = (store, {questionId}) => \r\n{\r\n\tconst examData = getExamData(store);\r\n\r\n\tconst contentData = getContentData(examData);\r\n\tconst mappingsData = getMappingsData(contentData);\r\n\tconst sectionId = getParentSectionId(mappingsData)(questionId);\r\n\r\n\tconst text = getQuestionScenarioText(contentData)(questionId);\r\n\tconst questionNumberInSection = getQuestionNoInSection(contentData)(sectionId, questionId);\r\n\r\n\tconst showScenario = getShowScenario(examData);\r\n\t\r\n\tconst settingsData = getSettingsData(store);\r\n\tconst clientSettingsData = getClientSettingsData(settingsData);\t\r\n\tconst rememberShowScenario = getRememberScenario(clientSettingsData);\t\r\n\r\n\treturn {text, questionNumberInSection, showScenario, rememberShowScenario};\r\n}\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setShowScenario: (value) => {\r\n dispatch({\r\n type: SET_SHOW_SCENARIO,\r\n value,\r\n });\r\n }\r\n});\r\n\r\nScenarioPanel = connect(mapStoreToProps, mapDispatchToProps)(ScenarioPanel);\r\nScenarioPanel = withStyles(styles)(ScenarioPanel);\r\nScenarioPanel = withMessages(ScenarioPanel);\r\n\r\n\r\n// Export\r\n// ------------------------------------------------\r\nexport {ScenarioPanel}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport IconButton from '@material-ui/core/IconButton'\r\nimport BookmarkIcon from '@material-ui/icons/Bookmark'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getQuestionsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getQuestionDataById} from 'redux/reducers/exam/content/questions/selectors'\r\nimport {isBookmarked} from 'redux/reducers/exam/content/questions/question/selectors'\r\n\r\n// redux (actions)\r\nimport {toggleBookmark} from './actions'\r\n\r\n\r\n// Bookmarker (not connected to store/styles)\r\n// -------------------------------------------------------------\r\n\r\nlet Bookmarker = ({selected, onClick, classes:{text, textSelected}}) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t \r\n\t);\r\n}\r\n\r\nBookmarker.propTypes = {\r\n\tselected: PropTypes.bool,\r\n\tonClick: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// Bookmarker (connected to store)\r\n// -------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store, {questionId}) =>\r\n{\r\n\tconst contentData = getContentData(getExamData(store));\r\n\tconst questionsData = getQuestionsData(contentData);\r\n\tconst questionData = getQuestionDataById(questionsData)(questionId);\r\n\treturn {selected: isBookmarked(questionData)};\r\n}\r\n\r\nconst mapDispatchToProps = (dispatch, {questionId}) => ({\r\n\tonClick: () => dispatch(toggleBookmark(questionId))\r\n});\r\n\r\nBookmarker = connect(mapStoreToProps, mapDispatchToProps)(Bookmarker);\r\n\r\n\r\n// Bookmarker (connected to styles)\r\n// ----------------------------------------\r\n\r\nconst styles = ({palette}) => ({\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t},\r\n\ttextSelected: {\r\n\t\tcolor: palette.secondary.main + \"!important\"\r\n\t}\r\n})\r\n\r\nBookmarker = withStyles(styles)(Bookmarker);\r\n\r\nBookmarker.propTypes = {\r\n\tquestionId: PropTypes.number.isRequired\r\n}\r\n\r\n\r\n// Export\r\n// -------------------------------------------------------------\r\nexport {Bookmarker}","\r\nimport {TOGGLE_BOOKMARK} from 'redux/reducers/exam/content/questions/question/action-types'\r\n\r\nconst toggleBookmark = (questionId) => ({\r\n\ttype: TOGGLE_BOOKMARK,\r\n\tquestionId\r\n});\r\n\r\nexport {toggleBookmark}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport Divider from '@material-ui/core/Divider'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getQuestionSectionText} from 'redux/reducers/exam/content/complex-selectors'\r\n\r\n\r\n// SectionText (not connected)\r\n// ----------------------------------------\r\n\r\nlet SectionText = ({text, classes}) =>\r\n{\r\n\tif (!text) { return null; }\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{text} \r\n\t\t\t\r\n\t\t \r\n\t)\r\n}\r\n\r\nSectionText.propTypes = {\r\n\ttext: PropTypes.string\r\n}\r\n\r\n\r\n// SectionText (connected to store)\r\n// ----------------------------------------\r\n\r\nconst mapStoreToProps = (store, {questionId}) => ({\r\n\ttext: getQuestionSectionText(getContentData(getExamData(store)))(questionId)\r\n});\r\n\r\nSectionText = connect(mapStoreToProps)(SectionText);\r\n\r\n\r\n// SectionText (connected to styles)\r\n// ---------------------------------------------------------------\r\n\r\nconst styles = ({palette, spacing}) => ({\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText\r\n\t},\r\n\tdivider: {\r\n\t\tmarginTop: spacing.unit,\r\n\t\tmarginBottom: spacing.unit\r\n\t}\r\n})\r\n\r\nSectionText = withStyles(styles)(SectionText);\r\n\r\n\r\nSectionText.propTypes = {\r\n\tquestionId: PropTypes.number.isRequired\r\n}\r\n\r\n\r\n// Export\r\n// ----------------------------------------\r\nexport {SectionText}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {MarksChip} from '../marks-chip'\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getQuestionsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getQuestionDataById} from 'redux/reducers/exam/content/questions/selectors'\r\nimport {getText, getMarks} from 'redux/reducers/exam/content/questions/question/selectors'\r\n\r\n\r\n// QuestionText (not connected to store)\r\n// ---------------------------------------------------------------\r\n\r\nlet QuestionText = ({text=\" \", marks, classes, questionId}) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{text}\r\n\t\t\t \r\n\t\t\t\r\n\t\t \r\n\t);\r\n}\r\n\r\nQuestionText.propTypes = {\r\n\ttext: PropTypes.string,\r\n\tmarks: PropTypes.number.isRequired,\r\n\tclasses: PropTypes.object.isRequired\r\n}\r\n\r\n\r\n// QuestionText (connected to store)\r\n// ---------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store, {questionId}) => {\r\n\tconst contentData = getContentData(getExamData(store));\r\n\tconst questionsData = getQuestionsData(contentData);\r\n\tconst questionData = getQuestionDataById(questionsData)(questionId);\r\n\treturn {text: getText(questionData), marks: getMarks(questionData)};\r\n};\r\n\r\nQuestionText = connect(mapStoreToProps)(QuestionText);\r\n\r\n\r\n// QuestionText (connected to styles)\r\n// ---------------------------------------------------------------\r\n\r\nconst styles = ({palette}) => ({\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText\r\n\t}\r\n})\r\n\r\nQuestionText = withStyles(styles)(QuestionText);\r\n\r\nQuestionText.propTypes = {\r\n\tquestionId: PropTypes.number.isRequired\r\n}\r\n\r\n\r\n// Export\r\n// ---------------------------------------------------------------\r\nexport {QuestionText}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport Divider from '@material-ui/core/Divider'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getQuestionsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getQuestionDataById} from 'redux/reducers/exam/content/questions/selectors'\r\nimport {getIntroText} from 'redux/reducers/exam/content/questions/question/selectors'\r\n\r\n\r\n// QuestionIntro (not connected to store/styles)\r\n// ----------------------------------------\r\n\r\nlet QuestionIntro = ({text, classes}) =>\r\n{\r\n\tif (!text) { return null; }\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{text} \r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\nQuestionIntro.propTypes = {\r\n\ttext: PropTypes.string.isRequired\r\n}\r\n\r\n\r\n// QuestionIntro (connected to store)\r\n// ----------------------------------------\r\n\r\nconst mapStoreToProps = (store, {questionId}) => {\r\n\tconst contentData = getContentData(getExamData(store));\r\n\tconst questionsData = getQuestionsData(contentData);\r\n\tconst questionData = getQuestionDataById(questionsData)(questionId);\r\n\treturn {text: getIntroText(questionData)};\r\n};\r\n\r\nQuestionIntro = connect(mapStoreToProps)(QuestionIntro);\r\n\r\n\r\n// QuestionIntro (connected to styles)\r\n// ----------------------------------------\r\n\r\nconst styles = ({palette}) => ({\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText\r\n\t}\r\n})\r\n\r\nQuestionIntro = withStyles(styles)(QuestionIntro);\r\n\r\n\r\nQuestionIntro.propTypes = {\r\n\tquestionId: PropTypes.number.isRequired\r\n}\r\n\r\n\r\n// Export\r\n// ----------------------------------------\r\nexport {QuestionIntro}","// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport Divider from '@material-ui/core/Divider'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getQuestionsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getQuestionDataById} from 'redux/reducers/exam/content/questions/selectors'\r\nimport {getEvidenceText} from 'redux/reducers/exam/content/questions/question/ict/selectors.js'\r\n\r\n\r\n// EvidenceText (not connected)\r\n// ----------------------------------------\r\n\r\nlet EvidenceText = ({text, classes}) =>\r\n{\r\n if (!text) { return null; }\r\n\r\n return (\r\n \r\n {text} \r\n \r\n \r\n );\r\n};\r\n\r\nEvidenceText.propTypes = {\r\n text: PropTypes.string\r\n}\r\n\r\n\r\n// EvidenceText (connected to store)\r\n// ----------------------------------------\r\n\r\nconst mapStoreToProps = (store, {questionId}) => {\r\n const contentData = getContentData(getExamData(store));\r\n const questionsData = getQuestionsData(contentData);\r\n const questionData = getQuestionDataById(questionsData)(questionId);\r\n return {text: getEvidenceText(questionData)};\r\n};\r\n\r\nEvidenceText = connect(mapStoreToProps)(EvidenceText);\r\n\r\n\r\n// EvidenceText (connected to styles)\r\n// ----------------------------------------\r\n\r\nconst styles = ({palette, spacing}) => ({\r\n text: {\r\n color: palette.background.contrastText\r\n },\r\n divider: {\r\n \tmarginTop: spacing.unit,\r\n\t marginBottom: spacing.unit\r\n }\r\n})\r\n\r\nEvidenceText = withStyles(styles)(EvidenceText);\r\n\r\nEvidenceText.propTypes = {\r\n questionId: PropTypes.number.isRequired\r\n}\r\n\r\n\r\n// Export\r\n// ----------------------------------------\r\nexport {EvidenceText}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\n\r\n// material-ui\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\n// react\r\nimport { Bookmarker } from \"./bookmarker\";\r\nimport { SectionText } from \"./section-text\";\r\nimport { QuestionText } from \"./question-text\";\r\nimport { QuestionIntro } from \"./question-intro\";\r\nimport { EvidenceText } from \"./evidence-text\";\r\n\r\n// QuestionPanel (not connected to styles)\r\n// ---------------------------------------------------\r\n\r\nlet QuestionPanel = ({ questionId, classes, noQuestionPanel }) => {\r\n const contents = (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n );\r\n\r\n return noQuestionPanel ? (\r\n contents\r\n ) : (\r\n {contents} \r\n );\r\n};\r\n\r\nQuestionPanel.propTypes = {\r\n classes: PropTypes.object.isRequired,\r\n questionId: PropTypes.number.isRequired,\r\n};\r\n\r\n// QuestionPanel (connected to styles)\r\n// ---------------------------------------------------\r\n\r\nconst styles = ({spacing, palette}) => {\r\n const _spacing = spacing.unit * 2;\r\n\r\n return {\r\n panel: {\r\n margin: _spacing,\r\n padding: _spacing,\r\n backgroundColor: palette.background.light,\r\n },\r\n wrapper: {\r\n display: \"flex\",\r\n flexDirection: \"row\",\r\n },\r\n leftSection: {\r\n flexGrow: 1,\r\n },\r\n rightSection: {\r\n flexShrink: 0,\r\n },\r\n };\r\n};\r\n\r\nQuestionPanel = withStyles(styles)(QuestionPanel);\r\n\r\n// Export\r\n// ---------------------------------------------------\r\nexport { QuestionPanel };\r\n","\r\n// npm\r\nimport React from 'react'\r\n\r\n// material-ui\r\nimport Paper from '@material-ui/core/Paper'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n\r\nconst getMessage = (disableNavigation, disableAnswering) => {\r\n if (disableNavigation && disableAnswering) {\r\n return 'Paused';\r\n }\r\n if (disableAnswering) {\r\n return 'Reading Time';\r\n }\r\n return '';\r\n}\r\n\r\nlet ExamStateCard = ({disableNavigation, disableAnswering, classes}) => {\r\n return (\r\n \r\n \r\n {getMessage(disableNavigation, disableAnswering)}\r\n \r\n \r\n ); \r\n}\r\n\r\n// ExamStateCard (connected to styles)\r\n// ---------------------------------------------------\r\n\r\nconst styles = ({spacing, palette}) => ({\r\n panel: {\r\n backgroundColor: palette.secondary.light,\r\n margin: spacing.unit * 2,\r\n padding: spacing.unit * 2,\r\n textAlign: 'center'\r\n },\r\n text: {\r\n color: palette.secondary.contrastText\r\n }\r\n})\r\n\r\nExamStateCard = withStyles(styles)(ExamStateCard);\r\n \r\nexport {ExamStateCard}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// react\r\nimport {DisableNavigationContext,\tDisableAnsweringContext} from 'components/pages/exam/disabled-context'\r\nimport {ExamStateCard} from './exam-state-card'\r\n\r\n\r\nconst CombinedContextConsumer = ({ children }) => \r\n(\r\n \r\n {disableNavigation => (\r\n \r\n {disableAnswering => children({ disableNavigation, disableAnswering })}\r\n \r\n )}\r\n \r\n);\r\n\r\nconst getExamStatePanel = (disableNavigation, disableAnswering) => \r\n{\r\n if (!disableAnswering && !disableNavigation) {\r\n return null;\r\n }\r\n const props = {\r\n disableNavigation, \r\n disableAnswering\r\n }\r\n return \r\n}\r\n\r\nconst ExamStatePanel = () => \r\n{\r\n return (\r\n \r\n {({ disableNavigation, disableAnswering }) => getExamStatePanel(disableNavigation, disableAnswering)}\r\n \r\n );\r\n}\r\n \r\n\r\nexport {ExamStatePanel}","import React, { Component } from 'react';\r\nimport {connect} from 'react-redux'\r\n\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getQuestionsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getQuestionDataById} from 'redux/reducers/exam/content/questions/selectors'\r\nimport {getNumber, isAnswered} from 'redux/reducers/exam/content/questions/question/selectors'\r\n\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport Fab from '@material-ui/core/Fab'\r\n\r\n\r\nconst styles = ({ spacing, palette }) => ({\r\n avatar: {\r\n background: palette.primary.main,\r\n width: \"32px\",\r\n height: \"32px\",\r\n marginBottom: spacing.unit * 2 \r\n },\r\n activeLabel: {\r\n\t\ttextDecoration: 'underline'\r\n\t},\r\n\tbutton: {\r\n\t\tmargin: spacing.unit / 2,\r\n marginBottom: spacing.unit,\r\n\t\tpadding: 0,\r\n\t},\r\n\tbuttonColor: {\r\n\t\tbackgroundColor: palette.primary.main + \"!important\",\r\n\t\tcolor: palette.primary.contrastText + \"!important\"\r\n\t},\r\n\tbuttonActiveColor: {\r\n\t\tbackgroundColor: palette.primary.dark + \"!important\",\r\n\t\tcolor: palette.primary.contrastText + \"!important\"\r\n\t},\r\n\tbuttonDisabledColor: {\r\n\t\tbackgroundColor: palette.primary.light + \"!important\",\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t} \r\n});\r\n\r\n\r\nclass QuestionNumber extends Component {\r\n state = { } \r\n\r\n displayFab(){\r\n const {answered, selected, classes, number} = this.props;\r\n\r\n const fabProps = {\r\n size: 'small',\r\n // onClick: props.onClick,\r\n // disabled: true,\r\n className: classes.button,\r\n color: answered ? 'primary' : 'default',\r\n classes: !selected ? {} : {label: classes.activeLabel}\r\n } \r\n\r\n return \r\n\t\t\t\t{number}\r\n\t\t\t \r\n }\r\n\r\n displayAvatar(){\r\n const {number, classes} = this.props;\r\n\r\n return {number} \r\n }\r\n\r\n render() { \r\n return this.displayFab();\r\n }\r\n}\r\n\r\nQuestionNumber = withStyles(styles)(QuestionNumber);\r\n\r\nconst mapStoreToProps = (store, {questionId}) => {\r\n\tconst contentData = getContentData(getExamData(store));\r\n\tconst questionsData = getQuestionsData(contentData);\r\n\tconst questionData = getQuestionDataById(questionsData)(questionId);\r\n\r\n\treturn {number: getNumber(questionData), answered: isAnswered(questionData)};\r\n};\r\n\r\nQuestionNumber = connect(mapStoreToProps)(QuestionNumber);\r\n \r\nexport {QuestionNumber};","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\nimport { check } from \"@xams-utils/check-types\";\r\nimport isEqual from \"lodash/isEqual\";\r\n\r\nimport {\r\n getMappingsAndSections,\r\n findCurrentSection,\r\n displayAllQuestionsInSection,\r\n} from \"../content-helper\";\r\nimport { setAudioElapsed, setAudioState } from \"./actions\";\r\n\r\nimport Divider from \"@material-ui/core/Divider\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport Paper from \"@material-ui/core/Paper\";\r\n\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport { AudioChip, CONSTANTS } from \"components/functional/audio/audio-chip\";\r\nimport { isAudio, getMediaPropsFromSection, urlHasChanged, hasAudioStateChanged } from \"components/functional/audio/audio-helper\";\r\n\r\nclass DisplayAllQuestionsBar extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleAudioElapsed = this.handleAudioElapsed.bind(this);\r\n this.handleAudioFinished = this.handleAudioFinished.bind(this);\r\n\r\n const state={}\r\n const { audio } = props;\r\n\r\n if (audio){\r\n state.audioProps=audio;\r\n }\r\n\r\n this.state={...state};\r\n }\r\n\r\n componentDidUpdate(prevProps){\r\n const {audio: prevAudio}=prevProps;\r\n const {audio}=this.props;\r\n\r\n if (urlHasChanged(audio,prevAudio)){\r\n this.setState({audioProps:audio?{...audio}:null});\r\n }\r\n else if (hasAudioStateChanged(audio,prevAudio)) { \r\n const {audioProps}=this.state;\r\n const newAudioProps={...audioProps, state:audio.state}\r\n\r\n this.setState({audioProps:newAudioProps});\r\n }\r\n }\r\n\r\n displayScenarioText(text) {\r\n if (!text) return null;\r\n\r\n const { classes } = this.props;\r\n\r\n return {text} ;\r\n }\r\n\r\n displayText(text) {\r\n if (!text) return null;\r\n\r\n const { classes } = this.props;\r\n\r\n return {text} ;\r\n }\r\n\r\n displayMedia() {\r\n const { classes } = this.props;\r\n const {audioProps}=this.state;\r\n\r\n if (audioProps) {\r\n\r\n //const props = { autoDelay: 1, ...media, autoPlay:false, canPause:true };\r\n const props = {\r\n autoDelay: 0,\r\n ...audioProps,\r\n onElapsed: this.handleAudioElapsed,\r\n onFinished: this.handleAudioFinished,\r\n elapsedUpdate: 5,\r\n };\r\n\r\n return (\r\n \r\n );\r\n }\r\n\r\n return null;\r\n }\r\n\r\n handleAudioElapsed(elapsed) {\r\n const { setElapsedAudio, currentSection } = this.props;\r\n\r\n setElapsedAudio(parseInt(currentSection.id), elapsed);\r\n }\r\n\r\n handleAudioFinished() {\r\n const { setStateAudio, currentSection } = this.props;\r\n setStateAudio(parseInt(currentSection.id), CONSTANTS.finished);\r\n } \r\n\r\n displayDivider() {\r\n return ;\r\n }\r\n\r\n hasMedia(){\r\n const {audio}=this.props;\r\n\r\n return audio;\r\n }\r\n\r\n render() {\r\n // return null;\r\n\r\n const { displayAllQuestions, currentSection, classes } = this.props;\r\n\r\n if (!displayAllQuestions) {\r\n return null;\r\n }\r\n\r\n const { scenarioText, text} = currentSection.data;\r\n if (!scenarioText && !text && !this.hasMedia()) return null;\r\n\r\n return (\r\n \r\n \r\n {this.displayScenarioText(scenarioText)}\r\n {scenarioText && text && this.displayDivider()}\r\n {this.displayText(text)}\r\n {(scenarioText || text) && this.hasMedia() && this.displayDivider()}\r\n {this.hasMedia() && this.displayMedia()}\r\n
\r\n \r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store, { currentQuestionId }) => {\r\n const { mappings, sections } = getMappingsAndSections(\r\n store,\r\n currentQuestionId\r\n );\r\n\r\n const currentSection = findCurrentSection(\r\n currentQuestionId,\r\n mappings,\r\n sections\r\n );\r\n\r\n const displayAllQuestions = displayAllQuestionsInSection(currentSection);\r\n\r\n return {\r\n displayAllQuestions,\r\n currentSection,\r\n canExpand: true,\r\n ...getMediaPropsFromSection(currentSection)\r\n };\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setElapsedAudio: (id, value) => {\r\n dispatch(setAudioElapsed(id, value));\r\n },\r\n setStateAudio: (id, value) => {\r\n dispatch(setAudioState(id, value));\r\n }, \r\n});\r\n\r\nDisplayAllQuestionsBar = connect(\r\n mapStoreToProps,\r\n mapDispatchToProps\r\n)(DisplayAllQuestionsBar);\r\n\r\nconst styles = ({ palette, spacing }) => {\r\n const _spacing = spacing.unit * 2;\r\n\r\n return {\r\n panel: {\r\n padding: `0px ${_spacing}px`,\r\n // marginBottom: 0,\r\n // padding: spacing.unit * 2,\r\n margin: `0px ${_spacing}px 0px`,\r\n backgroundColor: palette.background.light,\r\n },\r\n root: {\r\n padding: _spacing,\r\n backgroundColor: palette.background.default,\r\n },\r\n\r\n text: {\r\n margin: `${_spacing}px 0px`,\r\n },\r\n divider: {\r\n marginTop: spacing.unit,\r\n marginBottom: spacing.unit,\r\n },\r\n };\r\n};\r\n\r\nDisplayAllQuestionsBar = withStyles(styles)(DisplayAllQuestionsBar);\r\n\r\nexport { DisplayAllQuestionsBar };\r\n","import {SET_QUESTION_ID_FROM_QUESTION} from 'redux/reducers/exam/content/action-types'\r\nimport {SET_AUDIO_ELAPSED, SET_AUDIO_STATE} from \"redux/reducers/exam/content/sections/section/action-types\"\r\nimport { CONSTANTS } from \"components/functional/audio/audio-chip\";\r\n\r\nconst setQuestionIdSetFromQuestion = (value) => ({\r\n\ttype: SET_QUESTION_ID_FROM_QUESTION,\r\n\tvalue,\r\n});\r\n\r\nconst setAudioElapsed = (id, value) => ({\r\n\ttype: SET_AUDIO_ELAPSED,\r\n\tid,\r\n\tvalue,\r\n});\r\n\r\nconst setAudioState = (id, value) => ({\r\n\ttype: SET_AUDIO_STATE,\r\n\tid,\r\n\tvalue,\r\n});\r\n\r\nconst setAudioStateFinish = (id) => ({\r\n\ttype: SET_AUDIO_STATE,\r\n\tid,\r\n\tvalue: CONSTANTS.finished,\r\n});\r\n\r\nconst setAudioStatePlay = (id) => ({\r\n\ttype: SET_AUDIO_STATE,\r\n\tid,\r\n\tvalue: CONSTANTS.playing,\r\n});\r\n\r\nconst setAudioStatePause = (id) => ({\r\n\ttype: SET_AUDIO_STATE,\r\n\tid,\r\n\tvalue: CONSTANTS.paused,\r\n});\r\n\r\n\r\nexport {setQuestionIdSetFromQuestion, setAudioElapsed, setAudioState, setAudioStateFinish, setAudioStatePause, setAudioStatePlay}","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { setQuestionIdSetFromQuestion } from \"./actions\";\r\n\r\nimport { QuestionPanel } from \"../question_panel/panel\";\r\nimport { AnswerPanel } from \"../answer_panel/panel\";\r\nimport { QuestionNumber } from \"./question-number\";\r\nimport { DisplayAllQuestionsBar } from \"./display-all-questions-bar\";\r\n\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nconst isFirstQuestionInSection = (currentQuestionId, currentSection) => {\r\n if (currentSection) {\r\n const { questions } = currentSection;\r\n\r\n if (check.nonEmptyArray(questions)) {\r\n return questions[0] === currentQuestionId;\r\n }\r\n }\r\n\r\n return false;\r\n};\r\n\r\nclass DisplayAllQuestionsPanel extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.divRefs = [];\r\n const { currentSection } = props;\r\n const { questions } = currentSection;\r\n\r\n this.barRef = React.createRef();\r\n for (let i = 0; i < questions.length; i++) {\r\n this.divRefs[questions[i]] = React.createRef();\r\n }\r\n\r\n this.scrollToQuestion = this.scrollToQuestion.bind(this);\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n const {\r\n currentQuestionId: previousQuestionId,\r\n currentSection: prevCurrentSection,\r\n } = prevProps;\r\n const {\r\n currentQuestionId,\r\n currentSection,\r\n questionIdSetFromQuestion,\r\n clearQuestionIdSetFromQuestion,\r\n } = this.props;\r\n\r\n if (questionIdSetFromQuestion) {\r\n clearQuestionIdSetFromQuestion();\r\n } else {\r\n if (currentQuestionId !== previousQuestionId) {\r\n const { id: prevSectionId } = prevCurrentSection;\r\n const { id: sectionId } = currentSection;\r\n\r\n if (sectionId !== prevSectionId) {\r\n const { questions } = currentSection;\r\n\r\n this.barRef = React.createRef();\r\n this.divRefs = [];\r\n for (let i = 0; i < questions.length; i++) {\r\n this.divRefs[questions[i]] = React.createRef();\r\n }\r\n const scrollQuestion = isFirstQuestionInSection(\r\n currentQuestionId,\r\n currentSection\r\n )\r\n ? -1\r\n : currentQuestionId;\r\n\r\n this.timer = setTimeout(() => {\r\n this.scrollToQuestion(scrollQuestion);\r\n }, 250);\r\n } else {\r\n this.scrollToQuestion(currentQuestionId);\r\n }\r\n }\r\n }\r\n }\r\n\r\n componentDidMount() {\r\n const { currentQuestionId, currentSection } = this.props;\r\n if (!isFirstQuestionInSection(currentQuestionId, currentSection)) {\r\n console.log(currentQuestionId);\r\n this.timer = setTimeout(() => {\r\n this.scrollToQuestion(currentQuestionId);\r\n }, 250);\r\n }\r\n }\r\n\r\n componentWillUnmount() {\r\n clearTimeout(this.timer);\r\n }\r\n\r\n scrollToQuestion(questionId, lastScroll = false) {\r\n const element =\r\n questionId == -1 ? this.barRef : this.divRefs[questionId];\r\n\r\n if (element && element.current) {\r\n element.current.scrollIntoView({ behavior: \"smooth\" });\r\n // element.current.scrollIntoViewIfNeeded(true);\r\n } else if (!lastScroll) {\r\n this.timer = setTimeout(() => {\r\n this.scrollToQuestion(questionId, true);\r\n }, 250);\r\n }\r\n }\r\n\r\n displayBar() {\r\n const { currentQuestionId, classes } = this.props;\r\n const divName = \"barDiv\";\r\n return (\r\n \r\n
\r\n \r\n \r\n );\r\n }\r\n\r\n displayQuestions() {\r\n const { currentQuestionId, currentSection, classes } = this.props;\r\n\r\n return currentSection.questions.map((questionId, index, questions) => {\r\n const props = { noQuestionPanel: true, questionId };\r\n const numberProps = {\r\n questionId,\r\n selected: questionId === currentQuestionId,\r\n };\r\n const divName = `questionDiv${index}`;\r\n const panelClass =\r\n index === questions.length - 1\r\n ? classes.lastPanel\r\n : classes.panel;\r\n\r\n return (\r\n \r\n
\r\n \r\n \r\n \r\n \r\n );\r\n });\r\n }\r\n\r\n render() {\r\n return (\r\n \r\n {this.displayBar()}\r\n {this.displayQuestions()}\r\n \r\n );\r\n }\r\n}\r\n\r\nconst styles = (theme) => {\r\n const spacing = theme.spacing.unit * 2;\r\n\r\n return {\r\n lastPanel: {\r\n margin: `0 ${spacing}px ${spacing}px`,\r\n // marginBottom: 0,\r\n padding: spacing,\r\n backgroundColor: theme.palette.background.light,\r\n },\r\n panel: {\r\n margin: `0 ${spacing}px`,\r\n // marginBottom: 0,\r\n padding: spacing,\r\n backgroundColor: theme.palette.background.light,\r\n },\r\n scrollTo: {\r\n display: \"inline-block\",\r\n marginBottom: spacing,\r\n },\r\n question: {\r\n display: \"flex\",\r\n justifyContent: \"flex-start\",\r\n width: \"100%\",\r\n \"&>div:first-child\": {\r\n marginRight: spacing,\r\n flexGrow: 0,\r\n },\r\n \"&>div:last-child\": {\r\n display: \"inline-block\",\r\n flexGrow: 1,\r\n },\r\n },\r\n };\r\n};\r\n\r\nDisplayAllQuestionsPanel = withStyles(styles)(DisplayAllQuestionsPanel);\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n clearQuestionIdSetFromQuestion: () => {\r\n dispatch(setQuestionIdSetFromQuestion(false));\r\n },\r\n});\r\n\r\nDisplayAllQuestionsPanel = connect(\r\n undefined,\r\n mapDispatchToProps\r\n)(DisplayAllQuestionsPanel);\r\n\r\nexport { DisplayAllQuestionsPanel };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getScheduleUserId } from \"redux/reducers/schedules/schedule/selectors\";\r\nimport { getExamData, getSchedulesData } from \"redux/reducers/selectors\";\r\nimport { getScheduleDataByExamGuid } from \"redux/reducers/schedules/selectors\";\r\nimport { getGuid } from \"redux/reducers/exam/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getShowQuestionIdInPlayer } from \"redux/reducers/settings/client/selectors\";\r\n\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport Typography from \"@material-ui/core/Typography\";\r\n\r\nclass ExamIdMarker extends Component {\r\n state = {};\r\n render() {\r\n const { classes, show, scheduleUserId } = this.props;\r\n\r\n if (scheduleUserId && show) {\r\n\r\n return (\r\n \r\n {scheduleUserId} \r\n
\r\n );\r\n }\r\n return null;\r\n }\r\n}\r\n\r\nconst styles = ({ spacing }) => {\r\n return {\r\n root: {\r\n display: \"flex\",\r\n justifyContent: \"flex-start\",\r\n paddingLeft: spacing.unit * 2,\r\n fontSize: \".75em\",\r\n marginTop: \"-12px\",\r\n marginBottom: \"-14px\",\r\n },\r\n };\r\n};\r\n\r\nExamIdMarker = withStyles(styles)(ExamIdMarker);\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const examGuid = getGuid(examData);\r\n\r\n const schedulesData = getSchedulesData(store);\r\n const scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n return {\r\n scheduleUserId: getScheduleUserId(scheduleData),\r\n show: getShowQuestionIdInPlayer(clientSettingsData),\r\n };\r\n};\r\n\r\nExamIdMarker = connect(mapStoreToProps)(ExamIdMarker);\r\n\r\nexport { ExamIdMarker };\r\n","// npm\r\nimport React, { Component } from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\n\r\n// react\r\nimport { AnswerPanel } from \"./answer_panel/panel\";\r\nimport { ScenarioPanel } from \"./scenario_panel/panel\";\r\nimport { QuestionPanel } from \"./question_panel/panel\";\r\nimport { ExamStatePanel } from \"./exam_state_panel/panel\";\r\nimport { DisplayAllQuestionsPanel } from \"./display_all_questions/display-all-questions-panel\";\r\nimport {\r\n DisableNavigationContext,\r\n DisableAnsweringContext,\r\n} from \"components/pages/exam/disabled-context\";\r\n\r\nimport { getMappingsAndSections, findCurrentSection, displayAllQuestionsInSection } from \"./content-helper\";\r\nimport { ExamIdMarker } from \"./exam_id_marker/exam-id-marker\";\r\n\r\nclass ExamContent extends Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.displayQuestions = this.displayQuestions.bind(this);\r\n }\r\n\r\n displayQuestions() {\r\n const { displayAllQuestions, currentQuestionId, currentSection, questionIdSetFromQuestion } =\r\n this.props;\r\n\r\n if (displayAllQuestions) {\r\n const props = {\r\n currentQuestionId,\r\n currentSection,\r\n questionIdSetFromQuestion,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n }\r\n\r\n render() {\r\n const { paused, reading } = this.props;\r\n const disableNavigation = paused;\r\n const disableAnswering = paused || reading;\r\n\r\n return (\r\n \r\n \r\n \r\n {this.displayQuestions()}\r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nExamContent.propTypes = {\r\n currentQuestionId: PropTypes.number.isRequired,\r\n reading: PropTypes.bool,\r\n paused: PropTypes.bool,\r\n};\r\n\r\nconst mapStoreToProps = (store) => {\r\n const { mappings, sections, questionIdSetFromQuestion, currentQuestionId } =\r\n getMappingsAndSections(store);\r\n\r\n const currentSection = findCurrentSection(\r\n currentQuestionId,\r\n mappings,\r\n sections\r\n );\r\n const displayAllQuestions = displayAllQuestionsInSection(currentSection);\r\n\r\n return {\r\n displayAllQuestions, \r\n currentSection,\r\n currentQuestionId,\r\n questionIdSetFromQuestion,\r\n };\r\n};\r\n\r\nExamContent = connect(mapStoreToProps)(ExamContent);\r\n\r\nexport { ExamContent };\r\n","// utils\nimport { api } from '../api';\n\n// constants\nimport { ENDPOINTS, RETRY_COUNT } from '../constants';\n\n\nconst getFiles = (formRunGUID) => {\n return api.get(ENDPOINTS.UPLOAD.FILES + '/' + formRunGUID, {});\n}\n\nconst uploadFile = (formRunGUID, file, progressListener = null) => {\n const payload = new FormData();\n payload.append('formRunGUID', formRunGUID);\n payload.append('file', file);\n\n return api.post(\n ENDPOINTS.UPLOAD.UPLOAD,\n payload,\n RETRY_COUNT,\n progressListener\n );\n};\n\nconst deleteFile = (guid) =>{\n return api.delete(ENDPOINTS.UPLOAD.DELETE + '/' + guid, {});\n}\n\nconst downloadFile = (guid) => {\n return api.download(`${ENDPOINTS.UPLOAD.DOWNLOAD}/${guid}`, {});\n}\n\n\nconst uploadApi = {\n getFiles,\n uploadFile,\n deleteFile,\n downloadFile\n}\n\nexport {uploadApi}","// utils\r\nimport { uploadApi } from 'libs/api/interface/api-upload';\r\nimport { Console, LOG_TYPES } from 'custom/console';\r\n\r\nimport {\r\n activityLogger,\r\n ACTIVITIES,\r\n} from \"libs/activity_logger/activity-logger\";\r\n\r\nlet uploadObject = { files: [], index: 0, oldFileIndex: -1 };\r\n\r\nconst mapApiJsonToFile = json => {\r\n return {\r\n index: json.guid,\r\n name: json.fileName,\r\n type: json.mimeType,\r\n date: json.dateTimeUploaded,\r\n size: json.fileSize\r\n };\r\n};\r\n\r\nexport function getFiles(formRunGuid, onSuccess, onFailure) {\r\n uploadObject.finishedCallback = onSuccess;\r\n uploadObject.failureCallback = onFailure;\r\n uploadApi.getFiles(formRunGuid).then(filesGot, filesGotFailed);\r\n}\r\nfunction filesGot(response) {\r\n const parsedResponse = JSON.parse(response);\r\n let files = [];\r\n for (let i = 0; i < parsedResponse.length; i++)\r\n files.push(mapApiJsonToFile(parsedResponse[i]));\r\n uploadObject.finishedCallback(files);\r\n}\r\nfunction filesGotFailed(response) {\r\n uploadObject.failureCallback([]);\r\n}\r\n\r\nexport function deleteFile(file, deletedCallback, failedCallback) {\r\n if (file){\r\n const {name, size, type} = file;\r\n\r\n activityLogger.log(ACTIVITIES.DELETE_UPLOAD_ONLINE, {name, size, fileType: type});\r\n }\r\n\r\n uploadObject.file = file;\r\n uploadObject.deletedCallback = deletedCallback;\r\n uploadObject.failedCallback = failedCallback;\r\n uploadApi.deleteFile(file.index).then(fileDeleted, fileDeletedFailed);\r\n}\r\nfunction fileDeleted() {\r\n uploadObject.deletedCallback(uploadObject.file);\r\n}\r\nfunction fileDeletedFailed() {\r\n uploadObject.failedCallback(uploadObject.file);\r\n}\r\n\r\nexport function uploadFiles(\r\n formRunGuid,\r\n files,\r\n progressCallback = null,\r\n uploadedCallback,\r\n failedCallback\r\n) {\r\n uploadObject.files = files;\r\n uploadObject.uploadedSoFar = 0;\r\n uploadObject.uploadedTotal = 0;\r\n for (let i = 0; i < files.length; i++) {\r\n //const size=files[i].file===undefined?files[i].size:\r\n uploadObject.uploadedTotal += files[i].file.size;\r\n }\r\n uploadObject.formRunGuid = formRunGuid;\r\n uploadObject.index = 0;\r\n uploadObject.progressCallback = progressCallback;\r\n uploadObject.uploadedCallback = uploadedCallback;\r\n uploadObject.failedCallback = failedCallback;\r\n uploadObject.oldFileIndex = -1;\r\n uploadFile();\r\n}\r\n\r\nfunction uploadFile() {\r\n const firstFileUploaded = uploadObject.index === 0;\r\n if (firstFileUploaded && uploadObject.progressCallback !== null) {\r\n uploadObject.progressCallback(0);\r\n }\r\n const fileToUpload =\r\n uploadObject.files[uploadObject.index].file !== undefined\r\n ? uploadObject.files[uploadObject.index].file\r\n : uploadObject.files[uploadObject.index];\r\n \r\n if (fileToUpload){\r\n const {name, size, type} = fileToUpload;\r\n\r\n activityLogger.log(ACTIVITIES.UPLOAD_ONLINE, {name, size, fileType: type});\r\n }\r\n\r\n uploadApi\r\n .uploadFile(\r\n uploadObject.formRunGuid,\r\n fileToUpload,\r\n uploadObject.progressCallback !== null ? fileUploadProgress : null\r\n )\r\n .then(fileUploaded, fileUploadedFail);\r\n}\r\nconst fileUploadProgress = uploaded => {\r\n const uploadedSoFar = uploadObject.uploadedSoFar + uploaded;\r\n const percent = parseInt(\r\n (uploadedSoFar / uploadObject.uploadedTotal) * 100,\r\n 10\r\n );\r\n Console.log('fileUploadProgress', LOG_TYPES.TEMP);\r\n Console.log('uploaded, ' + uploaded, LOG_TYPES.TEMP);\r\n Console.log('uploadedSoFar, ' + uploadedSoFar, LOG_TYPES.TEMP);\r\n Console.log('uploadedTotal, ' + uploadObject.uploadedTotal, LOG_TYPES.TEMP);\r\n Console.log('percent, ' + percent, LOG_TYPES.TEMP);\r\n if (uploadObject.progressCallback != null)\r\n uploadObject.progressCallback(percent);\r\n};\r\nfunction fileUploaded(response) {\r\n const parsedResponse = JSON.parse(response);\r\n const fileToUpload =\r\n uploadObject.files[uploadObject.index].file !== undefined\r\n ? uploadObject.files[uploadObject.index].file\r\n : uploadObject.files[uploadObject.index];\r\n const uploadedFile = mapApiJsonToFile(parsedResponse);\r\n uploadObject.uploadedSoFar += uploadedFile.size;\r\n let position = -1;\r\n if (uploadObject.oldFileIndex !== -1) {\r\n position = uploadObject.oldFileIndex + uploadObject.index;\r\n // tempFiles.splice(position, 0, uploadedFile);\r\n } else {\r\n position = -1;\r\n // tempFiles.push(uploadedFile);\r\n }\r\n uploadObject.index++;\r\n const finishedUpload = uploadObject.index === uploadObject.files.length;\r\n uploadObject.uploadedCallback(\r\n { fileToUploadIndex: fileToUpload.index, uploadedFile, position },\r\n finishedUpload\r\n );\r\n if (!finishedUpload) uploadFile();\r\n}\r\nfunction fileUploadedFail(response) {\r\n const fileToUpload =\r\n uploadObject.files[uploadObject.index].file !== undefined\r\n ? uploadObject.files[uploadObject.index].file\r\n : uploadObject.files[uploadObject.index];\r\n uploadObject.failedCallback({\r\n fileToUploadIndex: fileToUpload.index,\r\n name: fileToUpload.name\r\n });\r\n}\r\n\r\nexport function replaceFiles(\r\n formRunGuid,\r\n fileToReplace,\r\n fileToReplacePosition,\r\n filesToUpload,\r\n deleteCallback,\r\n progressCallback,\r\n finishedCallback\r\n) {\r\n uploadObject.files = filesToUpload;\r\n uploadObject.uploadedSoFar = 0;\r\n uploadObject.uploadedTotal = 0;\r\n for (let i = 0; i < filesToUpload.length; i++) {\r\n uploadObject.uploadedTotal += filesToUpload[i].size;\r\n }\r\n uploadObject.formRunGuid = formRunGuid;\r\n uploadObject.index = 0;\r\n uploadObject.deleteCallback = deleteCallback;\r\n uploadObject.progressCallback = progressCallback;\r\n uploadObject.uploadedCallback = finishedCallback;\r\n uploadObject.oldFileIndex = fileToReplacePosition;\r\n deleteFile(fileToReplace, replaceFilesUpload);\r\n}\r\nfunction replaceFilesUpload(file) {\r\n uploadObject.deleteCallback(file);\r\n uploadFile();\r\n}\r\n\r\n\r\nexport function downloadFile(uploadGuid) {\r\n uploadApi.downloadFile(uploadGuid).then(({blob, fileName}) => {\r\n const a = document.createElement('a');\r\n a.href = window.URL.createObjectURL(blob);\r\n a.download = fileName;\r\n a.target = \"_blank\";\r\n a.dispatchEvent(new MouseEvent('click'));\r\n });\r\n}","export const needToCheckProp = prop => {\r\n if (prop === null) return false;\r\n if (Array.isArray(prop)) return prop.length > 0;\r\n if (typeof prop === 'number') return prop > 0;\r\n return false;\r\n};\r\n\r\nexport const getFirstFileType = fileType => {\r\n const fileTypes = fileType.split('/');\r\n return fileTypes.length === 0 ? '' : fileTypes[0];\r\n};\r\n\r\nexport const getFileExtension = fileName => fileName.split('.').pop();\r\n","import React, { Component } from 'react';\r\nimport withStyles from '@material-ui/core/styles/withStyles';\r\n\r\nconst styles = theme => ({\r\n dragHighlight: { backgroundColor: theme.palette.primary.main }\r\n});\r\n\r\nclass FileDragAndDrop extends Component {\r\n state = { dragging: false, highlightClass: null };\r\n dropRef = React.createRef();\r\n handleDrag = e => {\r\n if (this.props.disabled) return;\r\n e.preventDefault();\r\n e.stopPropagation();\r\n };\r\n handleDragIn = e => {\r\n if (this.props.disabled) return;\r\n e.preventDefault();\r\n e.stopPropagation();\r\n this.dragCounter++;\r\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {\r\n this.setState({ dragging: true });\r\n }\r\n this.handleHighlight(true);\r\n };\r\n handleDragOut = e => {\r\n if (this.props.disabled) return;\r\n e.preventDefault();\r\n e.stopPropagation();\r\n this.dragCounter--;\r\n if (this.dragCounter > 0) return;\r\n this.setState({ dragging: false });\r\n this.handleHighlight(false);\r\n };\r\n handleDrop = e => {\r\n if (this.props.disabled) return;\r\n e.preventDefault();\r\n e.stopPropagation();\r\n this.setState({ drag: false });\r\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\r\n this.props.onFileDrop(e.dataTransfer.files);\r\n e.dataTransfer.clearData();\r\n this.dragCounter = 0;\r\n }\r\n this.handleHighlight(false);\r\n };\r\n handleHighlight = highlight => {\r\n const className = highlight ? this.props.classes.dragHighlight : null;\r\n this.setState({ highlightClass: className });\r\n };\r\n componentDidMount() {\r\n this.dragCounter = 0;\r\n let div = this.dropRef.current;\r\n div.addEventListener('dragenter', this.handleDragIn);\r\n div.addEventListener('dragleave', this.handleDragOut);\r\n div.addEventListener('dragover', this.handleDrag);\r\n div.addEventListener('drop', this.handleDrop);\r\n }\r\n componentWillUnmount() {\r\n let div = this.dropRef.current;\r\n div.removeEventListener('dragenter', this.handleDragIn);\r\n div.removeEventListener('dragleave', this.handleDragOut);\r\n div.removeEventListener('dragover', this.handleDrag);\r\n div.removeEventListener('drop', this.handleDrop);\r\n }\r\n render() {\r\n return (\r\n \r\n {this.props.children}\r\n
\r\n );\r\n }\r\n}\r\n\r\nexport default withStyles(styles)(FileDragAndDrop);\r\n","import React, { Component } from 'react';\r\nimport ListItem from '@material-ui/core/ListItem';\r\nimport ListItemText from '@material-ui/core/ListItemText';\r\nimport ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';\r\nimport IconButton from '@material-ui/core/IconButton';\r\nimport Divider from '@material-ui/core/Divider';\r\nimport DeleteIcon from '@material-ui/icons/Delete';\r\nimport { getFirstFileType, getFileExtension } from './common';\r\n\r\nconst uploadImage = {\r\n display: 'inline-flex',\r\n borderRadius: '2px',\r\n border: '1px solid #eaeaea',\r\n marginBottom: '8px',\r\n marginRight: '8px',\r\n width: '60px',\r\n height: '60px',\r\n padding: '4px',\r\n boxSizing: 'border-box'\r\n};\r\nconst uploadDocument = {\r\n textAlign: 'center',\r\n paddingTop: '12px',\r\n width: '100%',\r\n height: 'calc(100% - 12px)',\r\n background: 'grey',\r\n textTransform: 'uppercase',\r\n fontWeight: 'bold'\r\n};\r\n\r\nconst isImage = file => getFirstFileType(file.type) === 'image';\r\n\r\nclass FileToUpload extends Component {\r\n state = {\r\n preview: isImage(this.props.file)\r\n ? URL.createObjectURL(this.props.file)\r\n : null\r\n };\r\n componentWillUnmount = () => {\r\n if (this.state.preview !== null) URL.revokeObjectURL(this.state.preview);\r\n };\r\n handleDelete = () => {\r\n if (!this.props.disabled) this.props.onDelete(this.props.index);\r\n };\r\n getFilePreview = () => {\r\n const { file } = this.props;\r\n return this.state.preview !== null ? (\r\n \r\n ) : (\r\n \r\n
{getFileExtension(file.name)}
\r\n
\r\n );\r\n };\r\n render() {\r\n const { file, disabled } = this.props;\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n {this.getFilePreview()}\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nexport default FileToUpload;\r\n","import React, { Component } from 'react';\r\nimport withStyles from '@material-ui/core/styles/withStyles';\r\nimport Button from '@material-ui/core/Button';\r\nimport List from '@material-ui/core/List';\r\nimport ListSubheader from '@material-ui/core/ListSubheader';\r\nimport Card from '@material-ui/core/Card';\r\nimport Typography from '@material-ui/core/Typography';\r\nimport Divider from '@material-ui/core/Divider';\r\nimport DeleteIcon from '@material-ui/icons/Delete';\r\nimport CloudUploadIcon from '@material-ui/icons/CloudUpload';\r\nimport FileDragAndDrop from './file-drag-and-drop';\r\nimport FileToUpload from './file-to-upload';\r\nimport './files-to-upload-list.css';\r\n\r\nconst styles = theme => ({\r\n button: {\r\n margin: theme.spacing.unit\r\n },\r\n rightIcon: {\r\n marginLeft: theme.spacing.unit\r\n },\r\n cardDivider: {\r\n listStyleType: 'none'\r\n },\r\n list: {\r\n maxHeight: 'calc(33vh - 20px)',\r\n overflow: 'auto',\r\n backgroundColor: theme.palette.background.paper\r\n },\r\n listSubHeader: {\r\n backgroundColor: 'inherit',\r\n fontWeight: 'bold',\r\n color: theme.palette.primary.text\r\n }\r\n});\r\n\r\nclass FilesToUploadList extends Component {\r\n state = { disabled: false };\r\n handleFileDrop = files => {\r\n this.props.onAddFilesToUpload(files);\r\n };\r\n handleFileSelect = e => {\r\n this.props.onAddFilesToUpload(e.target.files);\r\n };\r\n handleDeleteAll = () => {\r\n this.props.onDelete(-1);\r\n };\r\n isUploading = () => {\r\n return this.props.uploadProgress !== -1;\r\n };\r\n getUploadBoxContent = () => {\r\n const { disabled, disableUploading, isDeleting } = this.props;\r\n if (this.isUploading())\r\n return (\r\n \r\n \r\n Uploading\r\n {this.props.uploadProgress > 0\r\n ? ' ' + this.props.uploadProgress + '%'\r\n : ''}\r\n \r\n
\r\n );\r\n else if (isDeleting)\r\n return (\r\n \r\n Deleting... \r\n
\r\n );\r\n else {\r\n return (\r\n \r\n \r\n \r\n \r\n {`Drop file${\r\n this.props.multiple ? 's' : ''\r\n } here or click to upload`}\r\n \r\n \r\n \r\n );\r\n }\r\n };\r\n getUploadOptions = () => {\r\n const { classes, disableUploading, instantUpload, isDeleting } = this.props;\r\n const buttonDisabled =\r\n this.isUploading() ||\r\n this.props.files.length === 0 ||\r\n disableUploading ||\r\n isDeleting;\r\n if (!instantUpload)\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n Remove All Files\r\n \r\n \r\n \r\n Upload\r\n \r\n \r\n
\r\n \r\n \r\n \r\n \r\n {`${\r\n this.props.files.length === 0 ? 'No' : this.props.files.length\r\n } file${this.props.files.length !== 1 ? 's' : ''} to upload`}\r\n \r\n }\r\n >\r\n {this.props.files.map(f => (\r\n \r\n ))}\r\n
\r\n \r\n );\r\n };\r\n render() {\r\n const { disableUploading, isDeleting } = this.props;\r\n return (\r\n \r\n \r\n {this.getUploadBoxContent()}
\r\n \r\n {this.getUploadOptions()}\r\n \r\n );\r\n }\r\n}\r\nFilesToUploadList.defaultProps = {\r\n multiple: false,\r\n maxFilesToUpload: 0,\r\n maxFileSize: 0,\r\n minFileSize: 0,\r\n accepts: []\r\n};\r\nexport default withStyles(styles)(FilesToUploadList);\r\n","import React, { Component } from 'react';\r\nimport ListItem from '@material-ui/core/ListItem';\r\nimport ListItemText from '@material-ui/core/ListItemText';\r\nimport ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';\r\nimport IconButton from '@material-ui/core/IconButton';\r\nimport Divider from '@material-ui/core/Divider';\r\nimport Avatar from '@material-ui/core/Avatar';\r\nimport Tooltip from '@material-ui/core/Tooltip';\r\nimport ImageIcon from '@material-ui/icons/Image';\r\nimport DocumentIcon from '@material-ui/icons/Description';\r\nimport DeleteIcon from '@material-ui/icons/Delete';\r\nimport ReplaceIcon from '@material-ui/icons/Cached';\r\nimport FileDragAndDrop from './file-drag-and-drop';\r\nimport { getFirstFileType } from './common';\r\nimport Moment from 'react-moment';\r\nimport {downloadFile} from './services/upload-service';\r\n\r\n\r\nconst getFileIcon = file =>\r\n{\r\n if (getFirstFileType(file.type) === 'image') { return ; }\r\n else { return ; }\r\n}\r\n\r\n\r\nclass UploadedFile extends Component\r\n{\r\n constructor(props)\r\n {\r\n super(props);\r\n\r\n this.state = {};\r\n this.uploadInputRef = null;\r\n this.initializeBoundMethods();\r\n }\r\n\r\n initializeBoundMethods()\r\n {\r\n this.handleDelete = () => {\r\n this.props.onDelete(this.props.file.index);\r\n }\r\n\r\n this.handleReplace = (files) => {\r\n const newFiles = [];\r\n for (let i = 0; i < files.length; i++) newFiles.push(files[i]);\r\n this.props.onReplace(this.props.file.index, newFiles);\r\n }\r\n\r\n this.handleFileSelect = (e) => {\r\n this.handleReplace(e.target.files); \r\n }\r\n }\r\n\r\n render()\r\n {\r\n const {onReplace} = this.props;\r\n const disableDragAndDrop = this.props.disableButtons || onReplace === null;\r\n\r\n return (\r\n \r\n \r\n \r\n {this.UploadedItem}\r\n \r\n {this.UploadInput}\r\n \r\n );\r\n }\r\n\r\n get UploadedItem()\r\n {\r\n return (\r\n downloadFile(this.props.file.index)}>\r\n {this.UploadedItemIcon}\r\n {this.UploadedItemText}\r\n {this.UploadedItemActions}\r\n \r\n );\r\n }\r\n\r\n get UploadedItemIcon()\r\n {\r\n return (\r\n \r\n {getFileIcon(this.props.file)}\r\n \r\n );\r\n }\r\n\r\n get UploadedItemText()\r\n {\r\n const {onReplace, file} = this.props;\r\n const itemWidth = onReplace === null ? 'calc(100% - 48px)' : '100%';\r\n\r\n const props = {\r\n primary: file.name,\r\n primaryTypographyProps: {noWrap: true, style: {width: itemWidth}},\r\n secondary: {file.date} \r\n }\r\n\r\n return ;\r\n }\r\n\r\n get UploadedItemActions()\r\n {\r\n return (\r\n \r\n \r\n {this.DeleteAction}\r\n {this.ReplaceAction}\r\n
\r\n \r\n );\r\n }\r\n\r\n get DeleteAction()\r\n {\r\n const {handleDelete, props:{disabled}} = this;\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n )\r\n }\r\n\r\n get ReplaceAction()\r\n {\r\n if (this.props.onReplace === null) { return null; }\r\n\r\n const {disabled} = this.props;\r\n const onClick = () => this.uploadInputRef.click();\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n )\r\n }\r\n\r\n get UploadInput()\r\n {\r\n const inputProps = {\r\n type: 'file',\r\n multiple: this.props.multiple,\r\n style: {visibility: 'hidden'},\r\n ref: (reference) => { this.uploadInputRef = reference; },\r\n onChange: this.handleFileSelect\r\n }\r\n\r\n return ;\r\n }\r\n}\r\n\r\n\r\nexport default UploadedFile;","import React, { Component } from 'react';\r\nimport withStyles from '@material-ui/core/styles/withStyles';\r\nimport List from '@material-ui/core/List';\r\nimport ListSubheader from '@material-ui/core/ListSubheader';\r\nimport Card from '@material-ui/core/Card';\r\nimport UploadedFile from './uploaded-file';\r\nimport { needToCheckProp } from './common';\r\nimport {MaterialScrollWrapper} from 'components/layout/scroll_wrapper/material-scroll-wrapper'\r\n\r\nconst styles = theme => ({\r\n list2: {\r\n maxHeight: '33vh',\r\n backgroundColor: theme.palette.background.paper\r\n },\r\n list1: {\r\n maxHeight: '65vh',\r\n backgroundColor: theme.palette.background.paper\r\n },\r\n listSubHeader: {\r\n backgroundColor: 'inherit',\r\n fontWeight: 'bold',\r\n color: theme.palette.primary.text\r\n }\r\n});\r\n\r\nclass UploadedFileList extends Component {\r\n state = {\r\n deleteDialogOpen: false,\r\n fileNameToDelete: '',\r\n replaceDialogOpen: false,\r\n fileNameToReplace: ''\r\n };\r\n replaceFiles = [];\r\n handleDelete = fileIndex => {\r\n this.fileIndexToDelete = fileIndex;\r\n const index = this.props.files.findIndex(f => f.index === fileIndex);\r\n const content = `Do you wish to delete - ${this.props.files[index].name}.`;\r\n this.props.onDialog(\r\n true,\r\n 'Delete Uploaded File',\r\n content,\r\n ['Yes', 'No'],\r\n this.handleDeleteConfirmed\r\n );\r\n };\r\n handleDeleteConfirmed = option => {\r\n this.props.onDialog(false);\r\n if (option === 'Yes') this.props.onDelete(this.fileIndexToDelete);\r\n };\r\n handleReplace = (fileIndex, files) => {\r\n this.fileIndexToReplace = fileIndex;\r\n this.replaceFiles = files;\r\n const index = this.props.files.findIndex(f => f.index === fileIndex);\r\n const notSameFileName =\r\n this.replaceFiles.length !== 1 ||\r\n this.props.files[index].name !== this.replaceFiles[0].name ? (\r\n \r\n {'with'}\r\n {this.replaceFiles.map((f, i) => (\r\n
{f.name}
\r\n ))}\r\n
\r\n ) : null;\r\n let content = (\r\n \r\n Do you wish to replace - {this.props.files[index].name}\r\n {notSameFileName}\r\n
\r\n );\r\n this.props.onDialog(\r\n true,\r\n 'Replace Uploaded File',\r\n content,\r\n ['Yes', 'No'],\r\n this.handleReplaceConfirmed\r\n );\r\n };\r\n handleReplaceConfirmed = option => {\r\n this.props.onDialog(false);\r\n if (option === 'Yes')\r\n this.props.onReplace(this.fileIndexToReplace, this.replaceFiles);\r\n };\r\n getListSubHeader = () => {\r\n const { gotFiles } = this.props;\r\n let subHeaderText = '';\r\n if (!gotFiles) subHeaderText = 'Loading...';\r\n else {\r\n subHeaderText +=\r\n this.props.files.length === 0 ? 'No' : this.props.files.length;\r\n subHeaderText += ' uploaded file';\r\n if (this.props.files.length !== 1) subHeaderText += 's';\r\n if (needToCheckProp(this.props.maxFiles))\r\n subHeaderText += ` (Max ${this.props.maxFiles})`;\r\n }\r\n return subHeaderText;\r\n };\r\n render() {\r\n const { classes } = this.props;\r\n return (\r\n \r\n \r\n \r\n {this.getListSubHeader()}\r\n \r\n }\r\n >\r\n {this.props.files.map(f => (\r\n \r\n ))}\r\n
\r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nexport default withStyles(styles)(UploadedFileList);\r\n","import React, { Component } from 'react';\r\nimport Button from '@material-ui/core/Button';\r\n\r\nclass CommonDialogOption extends Component {\r\n handleClick = () => {\r\n this.props.onSelect(this.props.index);\r\n };\r\n render() {\r\n return (\r\n \r\n {this.props.text}\r\n \r\n );\r\n }\r\n}\r\n\r\nexport default CommonDialogOption;\r\n","import React, { Component } from 'react';\r\nimport Dialog from '@material-ui/core/Dialog';\r\nimport DialogActions from '@material-ui/core/DialogActions';\r\nimport DialogContent from '@material-ui/core/DialogContent';\r\nimport DialogContentText from '@material-ui/core/DialogContentText';\r\nimport DialogTitle from '@material-ui/core/DialogTitle';\r\nimport CommonDialogOption from './common-dialog-option';\r\n\r\nclass CommonDialog extends Component {\r\n handleSelect = index => {\r\n this.props.onSelect(this.props.options[index], index, this.props.data);\r\n };\r\n render() {\r\n let { title, content, options } = this.props;\r\n if (options === undefined) options = [];\r\n if (content === undefined) content = '';\r\n if (title === undefined) title = '';\r\n return (\r\n \r\n {title} \r\n \r\n \r\n {content}\r\n \r\n \r\n \r\n {options.map((option, index) => (\r\n \r\n ))}\r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nexport default CommonDialog;\r\n","import React, { Component } from 'react';\r\nimport PropTypes from 'prop-types'\r\nimport withStyles from '@material-ui/core/styles/withStyles';\r\n\r\nimport {\r\n getFiles,\r\n deleteFile,\r\n uploadFiles,\r\n replaceFiles\r\n} from './services/upload-service';\r\nimport { needToCheckProp, getFirstFileType, getFileExtension } from './common';\r\nimport FilesToUploadList from './files-to-upload-list';\r\nimport UploadedFileList from './uploaded-file-list';\r\nimport CommonDialog from './common-dialog';\r\n\r\nconst styles = theme => ({\r\n uploadPanel: {\r\n backgroundColor: theme.palette.grey[300],\r\n width: '350px',\r\n height: 'calc(100% - 40px)',\r\n padding: '20px'\r\n }\r\n});\r\nconst UPLOADED = 'uploaded';\r\nconst TOUPLOAD = 'toupload';\r\nconst INVALID_FILE_TYPE = 1;\r\nconst FILE_TOO_BIG = 2;\r\nconst FILE_TOO_SMALL = 3;\r\nconst FILE_DUPLICATE = 5;\r\n\r\nconst isSameFileExtension = (fileExtension1, fileExtension2) => (\r\n fileExtension1.toLowerCase() === fileExtension2.toLowerCase()\r\n);\r\n\r\nconst matchesFileType = (file, fileType) => {\r\n if (file.type === fileType) {\r\n return true;\r\n }\r\n\r\n const fileExtension = getFileExtension(file.name);\r\n const allowedFileExtension = fileType.startsWith(\".\")\r\n ? fileType.substring(1)\r\n : fileType;\r\n if (isSameFileExtension(fileExtension, allowedFileExtension)) {\r\n return true;\r\n }\r\n\r\n const isFileType = fileType.length > 2 && fileType.slice(-2) === \"/*\";\r\n if (!isFileType) {\r\n return false;\r\n }\r\n const actualFileType1 = getFirstFileType(fileType);\r\n const actualFileType2 = getFirstFileType(file.type);\r\n return isSameFileExtension(actualFileType1, actualFileType2);\r\n};\r\n\r\nconst fileTypeOk = (file, accepts) => {\r\n if (!needToCheckProp(accepts)) {\r\n return true;\r\n }\r\n\r\n const isFileTypeOk = accepts.some((acceptedFileExtension) => {\r\n return matchesFileType(file, acceptedFileExtension);\r\n });\r\n\r\n return isFileTypeOk;\r\n};\r\n\r\nconst fileMinSizeOk = (fileSize, minFileSize) =>\r\n !needToCheckProp(minFileSize) || fileSize >= minFileSize;\r\nconst fileMaxSizeOk = (fileSize, maxFileSize) =>\r\n !needToCheckProp(maxFileSize) || fileSize < maxFileSize;\r\n\r\nclass UploadPanel extends Component {\r\n dropRef = React.createRef();\r\n events = [\r\n 'drag',\r\n 'dragstart',\r\n 'dragend',\r\n 'dragover',\r\n 'dragenter',\r\n 'dragleave',\r\n 'drop'\r\n ];\r\n checkUpload = {\r\n index: 0,\r\n files: [],\r\n uploads: [],\r\n replace: []\r\n };\r\n state = {\r\n uploadedFiles: [],\r\n filesToUpload: [],\r\n uploadProgress: -1,\r\n disableButtons: false,\r\n disableUpload: false,\r\n disableUploading: false,\r\n gotFiles: false,\r\n commonDialog: { open: false, title: '', content: '', options: [] }\r\n };\r\n componentDidMount() {\r\n let div = this.dropRef.current;\r\n this.events.forEach(e => div.addEventListener(e, this.handleDragEvent));\r\n this.fileToUploadIndex = 0;\r\n getFiles(this.props.formRunGuid, this.updateFiles, this.getFilesFailed);\r\n }\r\n updateFiles = files => {\r\n this.setState({\r\n uploadedFiles: files,\r\n gotFiles: true\r\n });\r\n };\r\n getFilesFailed = files => {\r\n const content = 'There was a problem getting the uploaded files.';\r\n this.handleDialog({\r\n open: true,\r\n title: 'Unable to get Uploaded Files',\r\n content,\r\n options: ['Ok'],\r\n data: { files },\r\n onSelect: this.getFilesFailedConfirmed\r\n });\r\n };\r\n getFilesFailedConfirmed = (confirmed, index, data) => {\r\n this.handleDialog({\r\n open: false\r\n });\r\n this.updateFiles(data.files);\r\n };\r\n componentWillUnmount() {\r\n let div = this.dropRef.current;\r\n this.events.forEach(e => div.removeEventListener(e, this.handleDragEvent));\r\n }\r\n handleDragEvent = e => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n return false;\r\n };\r\n handleAddFilesToUpload = files => {\r\n const { maxFilesToUpload: maxFiles, instantUpload } = this.props;\r\n const newFiles = [...this.state.filesToUpload];\r\n this.filesToUploadErrors = {\r\n index: 0,\r\n errors: []\r\n };\r\n const fileCount = this.props.multiple ? files.length : 1;\r\n for (let i = 0; i < fileCount; i++) {\r\n if (maxFiles > 0 && newFiles.length >= maxFiles) {\r\n this.filesToUploadErrors.errors.push({ errorCode: 4, file: files[i] });\r\n break;\r\n }\r\n let errorCode = this.checkFileOkToUpload(files[i]);\r\n if (errorCode !== 0)\r\n this.filesToUploadErrors.errors.push({ errorCode, file: files[i] });\r\n else {\r\n newFiles.push({ index: this.fileToUploadIndex, file: files[i] });\r\n this.fileToUploadIndex++;\r\n }\r\n }// NUMBER (0, ANY ELSE) BOOLEAN (TRUE/FALSE)\r\n const disableUpload = maxFiles > 0 && newFiles.length === maxFiles;\r\n this.setState({\r\n filesToUpload: newFiles,\r\n disableUpload\r\n });\r\n if (this.filesToUploadErrors.errors.length > 0)\r\n this.handleFilesToUploadError();\r\n else if (instantUpload && newFiles.length > 0) this.handleUpload(newFiles);\r\n };\r\n checkFileOkToUpload = file => {\r\n const { maxFileSize, minFileSize, accepts, rejects } = this.props;\r\n\r\n if (!fileTypeOk(file, accepts)) return INVALID_FILE_TYPE;\r\n if (rejects.length > 0 && fileTypeOk(file, rejects))\r\n return INVALID_FILE_TYPE;\r\n if (!fileMinSizeOk(file.size, minFileSize)) return FILE_TOO_SMALL;\r\n if (!fileMaxSizeOk(file.size, maxFileSize)) return FILE_TOO_BIG;\r\n if (this.fileNameExists(file.name, TOUPLOAD) !== -1) return FILE_DUPLICATE;\r\n return 0;\r\n };\r\n fileNameExists = (newFileName, typeOfFilesToCheck) => {\r\n const isUpload = typeOfFilesToCheck === UPLOADED;\r\n const files = isUpload\r\n ? this.state.uploadedFiles\r\n : this.state.filesToUpload;\r\n for (let i = 0; i < files.length; i++) {\r\n const checkFile = isUpload ? files[i].name : files[i].file.name;\r\n if (checkFile === newFileName) return isUpload ? files[i].index : i;\r\n }\r\n return -1;\r\n };\r\n handleFilesToUploadError = () => {\r\n const { maxFileSize, minFileSize } = this.props;\r\n const error = this.filesToUploadErrors.errors[\r\n this.filesToUploadErrors.index\r\n ];\r\n let title = 'Error';\r\n let content = error.file.name + ' ';\r\n if (error.errorCode === INVALID_FILE_TYPE) {\r\n title = 'Invalid File Type';\r\n content += 'has an invalid file type';\r\n } else if (error.errorCode === FILE_TOO_BIG) {\r\n title = 'Maxiumum File Size';\r\n content = `The size of ${\r\n error.file.name\r\n } is greater than the maximum file size of ${maxFileSize}`;\r\n } else if (error.errorCode === FILE_TOO_SMALL) {\r\n title = 'Minimum File Size';\r\n content = `The size of ${\r\n error.file.name\r\n } must be greater than the minimum file size of ${minFileSize}`;\r\n }\r\n this.handleDialog(\r\n true,\r\n title,\r\n content,\r\n ['Ok'],\r\n this.handleFilesToUploadErrorConfirmed\r\n );\r\n };\r\n handleFilesToUploadErrorConfirmed = () => {\r\n this.handleDialog(false);\r\n this.filesToUploadErrors.index++;\r\n const { instantUpload } = this.props;\r\n const { filesToUpload } = this.state;\r\n if (this.filesToUploadErrors.index < this.filesToUploadErrors.errors.length)\r\n this.handleFilesToUploadError();\r\n else if (instantUpload && filesToUpload.length > 0) this.handleUpload();\r\n };\r\n // if (errorCodes.length === 1 && errorCodes[0].errorCode === 5) {\r\n // this.replaceFile={...}\r\n // const content = `Do you wish to wish to replace file - ${\r\n // errorCodes[0].file.name\r\n // }.`;\r\n // this.props.onDialog(\r\n // true,\r\n // 'Replace Uploaded File',\r\n // content,\r\n // ['Yes', 'No'],\r\n // this.handleSameNameConfirmed\r\n // );\r\n // }\r\n\r\n handleDeleteFileToUpload = (fileIndex = -1) => {\r\n let newFiles = [];\r\n if (fileIndex !== -1) {\r\n newFiles = [...this.state.filesToUpload];\r\n newFiles = newFiles.filter(f => f.index !== fileIndex);\r\n }\r\n this.setState({\r\n filesToUpload: newFiles,\r\n disableUpload: false\r\n });\r\n };\r\n handleReplaceUploadedFiles = (fileIndex, files) => {\r\n const index = this.state.uploadedFiles.findIndex(\r\n f => f.index === fileIndex\r\n );\r\n const file = this.state.uploadedFiles[index];\r\n replaceFiles(\r\n this.props.formRunGuid,\r\n file,\r\n index,\r\n files,\r\n f => {\r\n this.uploadedFileHasBeenDeleted(f);\r\n },\r\n p => {\r\n this.handleUploadProgress(p);\r\n },\r\n (f, s) => {\r\n this.handleFileUploaded(f, s);\r\n }\r\n );\r\n };\r\n handleDeleteUploadedFile = (fileIndex = -1) => {\r\n const index = this.state.uploadedFiles.findIndex(\r\n f => f.index === fileIndex\r\n );\r\n const file = this.state.uploadedFiles[index];\r\n this.setState({\r\n disableButtons: true,\r\n isDeleting: true\r\n });\r\n deleteFile(\r\n file,\r\n f => {\r\n this.uploadedFileHasBeenDeleted(f);\r\n },\r\n this.uploadedFileHasBeenDeletedFailed\r\n );\r\n };\r\n uploadedFileHasBeenDeleted = file => {\r\n let newFiles = [...this.state.uploadedFiles];\r\n newFiles = newFiles.filter(f => f.index !== file.index);\r\n this.setState({\r\n uploadedFiles: newFiles,\r\n disableButtons: false,\r\n isDeleting: false\r\n });\r\n };\r\n uploadedFileHasBeenDeletedFailed = file => {\r\n const content = `There was a problem deleting the file - ${file.name}.`;\r\n this.handleDialog({\r\n open: true,\r\n title: 'Unable to Delete File',\r\n content,\r\n options: ['Ok'],\r\n onSelect: this.uploadedFileHasBeenDeletedFailedConfirmed\r\n });\r\n };\r\n uploadedFileHasBeenDeletedFailedConfirmed = () => {\r\n this.setState({\r\n disableButtons: false,\r\n isDeleting: false,\r\n commonDialog: { open: false }\r\n });\r\n };\r\n handleUpload = (newFiles = null) => {\r\n const files = newFiles !== null ? newFiles : [...this.state.filesToUpload];\r\n if (\r\n needToCheckProp(this.props.maxUploadedFiles) &&\r\n files.length + this.state.uploadedFiles.length >\r\n this.props.maxUploadedFiles\r\n ) {\r\n const numberOfFilesToUpload =\r\n this.props.maxUploadedFiles - this.state.uploadedFiles.length;\r\n files.splice(numberOfFilesToUpload, files.length - numberOfFilesToUpload);\r\n }\r\n this.checkDuplicateFileNames(files);\r\n };\r\n checkDuplicateFileNames = files => {\r\n this.checkUpload = {\r\n checkIndex: 0,\r\n replaceIndex: 0,\r\n files,\r\n upload: [],\r\n replace: []\r\n };\r\n this.checkDuplicateFileName();\r\n };\r\n checkDuplicateFileName = () => {\r\n const file = this.checkUpload.files[this.checkUpload.checkIndex];\r\n const existingUploadedFileIndex = this.fileNameExists(\r\n file.file.name,\r\n UPLOADED\r\n );\r\n if (existingUploadedFileIndex !== -1) {\r\n this.checkUpload.existingUploadedFileIndex = existingUploadedFileIndex;\r\n const content = `Do you wish to wish to replace - ${file.file.name}.`;\r\n this.handleDialog(\r\n true,\r\n 'Replace Uploaded File',\r\n content,\r\n ['Yes', 'No'],\r\n this.checkDuplicateFileNameConfirmed\r\n );\r\n } else this.checkDuplicateFileNameConfirmed();\r\n };\r\n checkDuplicateFileNameConfirmed = (confirmed = null) => {\r\n this.handleDialog(false);\r\n const file = this.checkUpload.files[this.checkUpload.checkIndex];\r\n if (confirmed === null) this.checkUpload.upload.push(file);\r\n else if (confirmed === 'Yes')\r\n this.checkUpload.replace.push({\r\n index: this.checkUpload.existingUploadedFileIndex,\r\n file\r\n });\r\n if (this.checkUpload.checkIndex === this.checkUpload.files.length - 1)\r\n this.handleUploadChecked();\r\n else {\r\n this.checkUpload.checkIndex++;\r\n this.checkDuplicateFileName();\r\n }\r\n };\r\n handleUploadChecked = () => {\r\n if (this.checkUpload.upload.length > 0) {\r\n this.setState({\r\n disableButtons: true\r\n });\r\n this.checkUpload.replaceIndex = -1;\r\n uploadFiles(\r\n this.props.formRunGuid,\r\n this.checkUpload.upload,\r\n p => {\r\n this.handleUploadProgress(p);\r\n },\r\n (f, s) => {\r\n this.handleFileUploaded(f, s);\r\n },\r\n this.handleFileUploadedFailed\r\n );\r\n } else if (this.checkUpload.replace.length > 0) this.handleCheckReplace();\r\n };\r\n handleCheckReplace = () => {\r\n const replace = this.checkUpload.replace[this.checkUpload.replaceIndex];\r\n this.handleReplaceUploadedFiles(replace.index, [replace.file.file]);\r\n };\r\n handleUploadProgress(progress) {\r\n this.setState({\r\n uploadProgress: progress\r\n });\r\n }\r\n handleFileUploaded = (uploadObject, uploadComplete = false) => {\r\n this.handleDeleteFileToUpload(uploadObject.fileToUploadIndex);\r\n let newFiles = [...this.state.uploadedFiles];\r\n if (uploadObject.position === -1) newFiles.push(uploadObject.uploadedFile);\r\n else newFiles.splice(uploadObject.position, 0, uploadObject.uploadedFile);\r\n const newProgress = uploadComplete ? -1 : this.state.uploadProgress;\r\n const disableButtons = !uploadComplete;\r\n const { maxUploadedFiles } = this.props;\r\n const disableUploading =\r\n maxUploadedFiles > 0 && newFiles.length >= maxUploadedFiles;\r\n this.setState({\r\n uploadedFiles: newFiles,\r\n uploadProgress: newProgress,\r\n disableButtons,\r\n disableUploading\r\n });\r\n if (this.checkUpload.replace.length > 0) {\r\n this.checkUpload.replaceIndex++;\r\n if (this.checkUpload.replaceIndex < this.checkUpload.replace.length)\r\n this.handleCheckReplace();\r\n }\r\n };\r\n handleFileUploadedFailed = uploadObject => {\r\n const content = `There was a problem uploading the file - ${\r\n uploadObject.name\r\n }.`;\r\n this.handleDialog({\r\n open: true,\r\n title: 'Unable to Upload File',\r\n content,\r\n options: ['Ok'],\r\n data: { uploadObject },\r\n onSelect: this.handleFileUploadedFailedConfirmed\r\n });\r\n };\r\n handleFileUploadedFailedConfirmed = (confirmed, index, data) => {\r\n this.handleDeleteFileToUpload(data.uploadObject.fileToUploadIndex);\r\n this.setState({\r\n uploadProgress: -1,\r\n disableButtons: false,\r\n disableUploading: false,\r\n commonDialog: { open: false }\r\n });\r\n };\r\n handleDialog = (\r\n isOpen = false,\r\n title = '',\r\n content = '',\r\n options = [],\r\n onSelect = null\r\n ) => {\r\n const commonDialog =\r\n typeof isOpen === 'boolean'\r\n ? {\r\n open: isOpen,\r\n title: title,\r\n content: content,\r\n options: options,\r\n onSelect: onSelect\r\n }\r\n : isOpen;\r\n this.setState({\r\n commonDialog\r\n });\r\n };\r\n\r\n render()\r\n {\r\n const {classes, multiple, maxUploadedFiles, instantUpload} = this.props;\r\n const pointerEvents = this.props.disabled ? 'none' : undefined;\r\n const {uploadPanel} = classes;\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n
\r\n );\r\n }\r\n}\r\n\r\nUploadPanel.propTypes = {\r\n disabled: PropTypes.bool,\r\n instantUpload: PropTypes.bool.isRequired,\r\n replaceFiles: PropTypes.bool.isRequired,\r\n multiple: PropTypes.bool.isRequired,\r\n maxFilesToUpload: PropTypes.number.isRequired,\r\n maxUploadedFiles: PropTypes.number.isRequired,\r\n maxFileSize: PropTypes.number.isRequired,\r\n minFileSize: PropTypes.number.isRequired,\r\n accepts: PropTypes.arrayOf(PropTypes.string).isRequired,\r\n rejects: PropTypes.arrayOf(PropTypes.string).isRequired\r\n}\r\n\r\nUploadPanel.defaultProps = {\r\n instantUpload: false,\r\n replaceFiles: false,\r\n multiple: false,\r\n maxFilesToUpload: 0,\r\n maxUploadedFiles: 0,\r\n maxFileSize: 0,\r\n minFileSize: 0,\r\n accepts: [],\r\n rejects: []\r\n};\r\n\r\nUploadPanel = withStyles(styles)(UploadPanel);\r\n\r\n// Export\r\n// -------------------------------------------------\r\nexport { UploadPanel };\r\n","const CALCULATOR_BUTTONS = [\r\n ['C', '<', '@', '+'],\r\n ['7', '8', '9', '*'],\r\n ['4', '5', '6', '-'],\r\n ['1', '2', '3', '/'],\r\n ['0', '.', '=']\r\n];\r\n\r\nconst OPERANDS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'];\r\n\r\nconst OPERATORS = ['+', '-', '/', '*'];\r\n\r\nconst CALCULATOR_EVALUATION = {\r\n scientific: 0,\r\n leftToRight: 1\r\n}\r\n\r\nconst CALCULATOR_PARTS={operand:1, operator:2}\r\n\r\nexport { CALCULATOR_BUTTONS, CALCULATOR_EVALUATION, OPERANDS, OPERATORS, CALCULATOR_PARTS };\r\n","import * as math from \"mathjs\";\r\n\r\nimport check from \"check-types\";\r\n\r\nimport { CALCULATOR_EVALUATION, OPERATORS, OPERANDS, CALCULATOR_PARTS } from \"./constants\";\r\n\r\nconst isOperand = (value) => {\r\n return OPERANDS.includes(value);\r\n};\r\n\r\nconst isOperator = (value) => {\r\n return OPERATORS.includes(value);\r\n};\r\n\r\nconst getResult = (value, evaluationType, decimalPlaces = 9) => {\r\n let result = value;\r\n\r\n if (evaluationType === CALCULATOR_EVALUATION.scientific)\r\n result = getScientificResult(value, decimalPlaces);\r\n else if (evaluationType === CALCULATOR_EVALUATION.leftToRight)\r\n result = getLeftToRightResult(value, decimalPlaces);\r\n\r\n return removeTrailingSpaces(result);\r\n};\r\n\r\nconst getScientificResult = (value, decimalPlaces) => {\r\n try {\r\n const answer = math.eval(value);\r\n return math.format(answer, {\r\n precision: decimalPlaces,\r\n notation: \"fixed\",\r\n });\r\n } catch (e) {\r\n return undefined;\r\n }\r\n};\r\n\r\nconst getLeftToRightResult = (value, decimalPlaces) => {\r\n const parts = getCalculationParts(value);\r\n let answer = 0;\r\n parts.forEach((part) => {\r\n answer = evaluatePart(answer, part);\r\n });\r\n return check.integer(answer) ? \"\" + answer : answer.toFixed(decimalPlaces);\r\n};\r\n\r\nconst getCalculationParts = (value) => {\r\n const parts = [];\r\n\r\n let numberStart = null;\r\n let sign = \"+\";\r\n let previousOperator = \"+\";\r\n\r\n for (let i = 0; i < value.length; i++) {\r\n const currentCharacter = value.charAt(i);\r\n\r\n if (isOperand(currentCharacter)) {\r\n if (!check.assigned(numberStart)) numberStart = i;\r\n } else if (isOperator(currentCharacter)) {\r\n if (!check.assigned(numberStart)) {\r\n sign = currentCharacter;\r\n } else {\r\n parts.push({\r\n operator: previousOperator,\r\n number: getNumberFromValue(\r\n value.substring(numberStart, i),\r\n sign\r\n ),\r\n });\r\n\r\n previousOperator = currentCharacter;\r\n sign = \"+\";\r\n numberStart = null;\r\n }\r\n }\r\n }\r\n\r\n if (check.assigned(numberStart)) {\r\n parts.push({\r\n operator: previousOperator,\r\n number: getNumberFromValue(\r\n value.substring(numberStart, value.length),\r\n sign\r\n ),\r\n });\r\n }\r\n return parts;\r\n};\r\n\r\nconst getNumberFromValue = (value, sign) => {\r\n const answer = value.includes(\".\") ? parseFloat(value) : parseInt(value);\r\n return sign === \"-\" ? -1 * answer : answer;\r\n};\r\n\r\nconst evaluatePart = (answer, part) => {\r\n if (part.operator === \"+\") answer += part.number;\r\n else if (part.operator === \"-\") answer -= part.number;\r\n else if (part.operator === \"*\") answer *= part.number;\r\n else if (part.operator === \"/\") answer /= part.number;\r\n\r\n return answer;\r\n};\r\n\r\nconst spaceOutResult = (value, result, button = '\"') => {\r\n let spacedResult = \"\";\r\n let previousOperator = true;\r\n let operatorFound = false;\r\n\r\n for (let i = 0; i < value.length; i++) {\r\n const currentCharacter = value.charAt(i);\r\n if (isOperator(currentCharacter) && !previousOperator) {\r\n if (i !== value.length - 1) {\r\n spacedResult += ` ${currentCharacter} `;\r\n previousOperator = true;\r\n operatorFound = true;\r\n }\r\n } else {\r\n previousOperator = false;\r\n spacedResult += currentCharacter;\r\n }\r\n }\r\n if (operatorFound) {\r\n spacedResult += ` ${button} ${result} `;\r\n } else if (button === \"}\") {\r\n spacedResult += ` \\u221a ${result} `;\r\n }\r\n\r\n return spacedResult;\r\n};\r\n\r\nconst plusMinusValue = (value) => {\r\n if (value === \"\") return value;\r\n\r\n const lastCharacter = value.substr(value.length - 1);\r\n let isPositive = true;\r\n let previous = 0;\r\n let numberStart = 0;\r\n\r\n if (isOperator(lastCharacter)) return value;\r\n for (let i = value.length - 1; i >= 0; i--) {\r\n const currentCharacter = value.charAt(i);\r\n if (!isOperand(currentCharacter)) {\r\n if (i === 0) {\r\n isPositive = currentCharacter === \"+\";\r\n numberStart = i + 1;\r\n } else {\r\n const previousCharacter = value.charAt(i - 1);\r\n if (isOperator(previousCharacter)) {\r\n isPositive = currentCharacter === \"+\";\r\n numberStart = i + 1;\r\n previous = i;\r\n } else {\r\n numberStart = i + 1;\r\n previous = i + 1;\r\n }\r\n }\r\n break;\r\n }\r\n }\r\n\r\n return (\r\n value.slice(0, previous) +\r\n (isPositive ? \"-\" : \"\") +\r\n value.slice(numberStart)\r\n );\r\n};\r\n\r\nconst decimalPointAllowed = (value) => {\r\n for (let i = value.length - 1; i >= 0; i--) {\r\n const currentCharacter = value.charAt(i);\r\n if (currentCharacter === \".\") return false;\r\n if (!isOperand(currentCharacter)) break;\r\n }\r\n\r\n return true;\r\n};\r\n\r\nconst operatorAllowed = (operator, value) => {\r\n if (value.length === 0) return true;\r\n\r\n const lastCharacter = value.charAt(value.length - 1);\r\n if (isOperand(lastCharacter)) return true;\r\n if (operator === \"-\") {\r\n if (value.length > 2) {\r\n const previousCharacter = value.charAt(value.length - 2);\r\n if (isOperand(previousCharacter)) return true;\r\n } else return true;\r\n }\r\n\r\n return false;\r\n};\r\n\r\nconst removeTrailingSpaces = (result) => {\r\n if (check.string(result)) {\r\n if (result.indexOf(\".\") !== -1) {\r\n for (let i = result.length; i >= 0; i--) {\r\n if (result[i] === \".\") return result.substring(0, i);\r\n else if (isNotZeroNumber(result[i]))\r\n return result.substring(0, i + 1);\r\n }\r\n }\r\n return result;\r\n } else return result.toString();\r\n};\r\n\r\nconst isNotZeroNumber = (value) => {\r\n const operands = OPERANDS.filter(\r\n (operand) => ![\"0\", \".\"].includes(operand)\r\n );\r\n return operands.includes(value);\r\n};\r\n\r\nconst isSEB = () => {\r\n return navigator.userAgent.indexOf(\"SEB\") !== -1;\r\n};\r\n\r\nconst addCalculatorSymbols = (value) => {\r\n return value.replace(/[*]/gi, \"\\u00d7\").replace(/[/]/gi, \"\\u00f7\");\r\n};\r\n\r\nconst removeCalculatorSymbols = (value) => {\r\n return value.replace(/[\\u00d7]/gi, \"*\").replace(/[\\u00f7]/gi, \"/\");\r\n};\r\n\r\nconst hasCalculatorSymbols = (value, ref) => {\r\n if (value && (value.indexOf(\"*\") !== -1 || value.indexOf(\"/\") !== -1)) {\r\n const workingsElement = ref.current;\r\n const { selectionStart } = workingsElement;\r\n\r\n return { value: addCalculatorSymbols(value), cursor: selectionStart };\r\n }\r\n return { value, cursor: null };\r\n};\r\n\r\nconst isValidPercentage = (value) => {\r\n const parts = getPercentageParts(value);\r\n\r\n if (!check.nonEmptyArray(parts)) return false;\r\n if (parts.length !== 3) return false;\r\n\r\n return (\r\n parts[0].type === CALCULATOR_PARTS.operand &&\r\n parts[1].type === CALCULATOR_PARTS.operator &&\r\n parts[2].type === CALCULATOR_PARTS.operand\r\n );\r\n};\r\n\r\nconst getPercentageParts = (value) => {\r\n let current = null;\r\n const parts = [];\r\n for (let i = 0; i < value.length; i++) {\r\n const currentChar = value.charAt(i);\r\n console.log(\"Current Char\", currentChar);\r\n if (isOperand(currentChar)) {\r\n console.log(\"Operand\", currentChar);\r\n if (!current) {\r\n current = {\r\n type: CALCULATOR_PARTS.operand,\r\n value: currentChar,\r\n };\r\n } else if (current.type === CALCULATOR_PARTS.operand) {\r\n current.value += currentChar;\r\n } else if (current.type === CALCULATOR_PARTS.operator) {\r\n parts.push({ ...current });\r\n current = {\r\n type: CALCULATOR_PARTS.operand,\r\n value: currentChar,\r\n };\r\n }\r\n } else if (isOperator(currentChar)) {\r\n console.log(\"Operator\", currentChar);\r\n if (!current) {\r\n current = {\r\n type: CALCULATOR_PARTS.operator,\r\n value: currentChar,\r\n };\r\n } else {\r\n parts.push({ ...current });\r\n current = {\r\n type: CALCULATOR_PARTS.operator,\r\n value: currentChar,\r\n };\r\n }\r\n } else if (value === \" \") {\r\n if (current) {\r\n parts.push({ ...current });\r\n }\r\n\r\n current = null;\r\n }\r\n console.log(\"parts\", parts);\r\n console.log(\"current\", current);\r\n }\r\n\r\n if (current) {\r\n parts.push({ ...current });\r\n }\r\n\r\n return parts;\r\n};\r\nconst processPercentage = (value, decimalPlaces = 8) => {\r\n const parts = getPercentageParts(value);\r\n const number1 = parseFloat(parts[0].value);\r\n const number2 = parseFloat(parts[2].value);\r\n const operator = parts[1].value;\r\n const percentage = number1 * (number2 / 100);\r\n let answer = null;\r\n\r\n if (operator === \"*\") {\r\n answer = percentage;\r\n } else if (operator === \"-\") {\r\n answer = number1 - percentage;\r\n } else if (operator === \"+\") {\r\n answer = number1 + percentage;\r\n }\r\n\r\n return answer\r\n ? check.integer(answer)\r\n ? \"\" + answer\r\n : removeTrailingSpaces(answer.toFixed(decimalPlaces))\r\n : null;\r\n};\r\n\r\nconst isValidSquareRoot = (value) => {\r\n return true;\r\n};\r\n\r\nconst processSquareRoot = (value, evaluationType, decimalPlaces = 9) => {\r\n const number = parseFloat(getResult(value, evaluationType, decimalPlaces));\r\n const answer = math.sqrt(number);\r\n\r\n const returnAnswer = answer\r\n ? check.integer(answer)\r\n ? \"\" + answer\r\n : removeTrailingSpaces(answer.toFixed(decimalPlaces))\r\n : null;\r\n\r\n return returnAnswer;\r\n};\r\n\r\nexport {\r\n isOperand,\r\n isOperator,\r\n getResult,\r\n plusMinusValue,\r\n spaceOutResult,\r\n decimalPointAllowed,\r\n operatorAllowed,\r\n isSEB,\r\n addCalculatorSymbols,\r\n removeCalculatorSymbols,\r\n hasCalculatorSymbols,\r\n isValidPercentage,\r\n processPercentage,\r\n isValidSquareRoot,\r\n processSquareRoot,\r\n};\r\n","// npm\r\nimport React from \"react\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { detectBrowser } from \"components/pages/app_initialization/initializing/validating_app_settings/browser_checker/detect-browser\";\r\nimport { errorApi } from \"libs/api/interface/api-error\";\r\n\r\n// material-ui\r\nimport TextField from \"@material-ui/core/TextField\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\n// react\r\nimport { BoundsListener } from \"components/functional/bounds-listener\";\r\n\r\n// utils\r\nimport { keyListener, KEYS } from \"utils/key-listener\";\r\nimport {\r\n addCalculatorSymbols,\r\n hasCalculatorSymbols,\r\n} from \"./calculator-helper\";\r\n\r\nconst disableArrowKeyListeners = () => {\r\n keyListener.disable(KEYS.LEFT_ARROW);\r\n keyListener.disable(KEYS.RIGHT_ARROW);\r\n};\r\n\r\nconst enableArrowKeyListeners = () => {\r\n keyListener.enable(KEYS.LEFT_ARROW);\r\n keyListener.enable(KEYS.RIGHT_ARROW);\r\n};\r\n\r\nconst getScreenHeight = () => {\r\n const height =\r\n window.innerHeight ||\r\n document.documentElement.clientHeight ||\r\n document.body.clientHeight;\r\n\r\n return height;\r\n};\r\n\r\nconst logError = (errorText) => {\r\n const { browser, version } = detectBrowser();\r\n\r\n errorApi.logError({\r\n orgID: null,\r\n userID: null,\r\n languageId: null,\r\n errorMessage: errorText,\r\n stackTrace: null,\r\n info: null,\r\n siteId: null,\r\n centreId: null,\r\n app: \"xams.UI.Web.Player\",\r\n browser: {\r\n browser,\r\n version,\r\n },\r\n });\r\n};\r\n\r\nconst calculateDefaultRowCount = () => {\r\n const screenHeight = getScreenHeight();\r\n\r\n // return calculateRowCount(screenHeight - 380);\r\n return calculateRowCount(screenHeight / 2);\r\n};\r\n\r\nconst calculateRowCount = (height) => {\r\n const textSize = 19;\r\n // const textSize = 16; // Error\r\n const rowCount = Math.floor((height - 37) / textSize) - 1;\r\n\r\n return rowCount < 1 ? 1 : rowCount;\r\n};\r\n\r\n// Workings (not connected to styles)\r\n// --------------------------------------------------------------------------\r\n\r\nclass Workings extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.state = {\r\n rowCount: 1,\r\n useListener: true,\r\n };\r\n\r\n this.updateCounter = 0;\r\n this.lastTime = 0;\r\n this.timeCounter = 0;\r\n\r\n this.checkTimeOk = () => {\r\n const currentTime = Date.now();\r\n const timeDiff = currentTime - this.lastTime;\r\n\r\n // console.log('checkTimeOk', this.timeCounter, currentTime, this.lastTime, timeDiff);\r\n\r\n if (timeDiff > 1000) {\r\n this.timeCounter = 0;\r\n } else {\r\n this.timeCounter++;\r\n if (this.timeCounter > 25) {\r\n console.log(\"set half screen\", this.timeCounter);\r\n\r\n this.setState({ rowCount: calculateDefaultRowCount() });\r\n\r\n return false;\r\n }\r\n }\r\n\r\n this.lastTime = currentTime;\r\n return true;\r\n };\r\n\r\n this.updateRowCount = (_, height) => {\r\n if (check.number(height)) {\r\n const { rowCount } = this.state;\r\n const newRowCount = calculateRowCount(height);\r\n\r\n if (rowCount !== newRowCount && rowCount > 0) {\r\n if (this.checkTimeOk()) {\r\n try {\r\n this.setState({ rowCount: newRowCount });\r\n } catch (e) {\r\n const newRowCount = calculateDefaultRowCount();\r\n\r\n console.log(\"turn off listener\", newRowCount);\r\n\r\n this.setState({\r\n rowCount: newRowCount,\r\n useListener: false,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n };\r\n }\r\n\r\n render() {\r\n const { classes, onChange } = this.props;\r\n const { useListener } = this.state;\r\n\r\n const textFieldProps = {\r\n multiline: true,\r\n value: this.props.value,\r\n label: \"Workings\",\r\n onChange: ({ target: { value } }) => this.props.onChange(value),\r\n // onChange: ({ target: { value } }) => {\r\n // const { value:_value, cursor } = hasCalculatorSymbols(\r\n // value,\r\n // this.props.workingsRef\r\n // );\r\n // if (cursor !== null) {\r\n // onChange(addCalculatorSymbols(_value), cursor);\r\n // } else {\r\n // onChange(addCalculatorSymbols(value));\r\n // }\r\n // },\r\n rows: this.state.rowCount,\r\n margin: \"none\",\r\n variant: \"outlined\",\r\n InputProps: {\r\n className: classes.inputText,\r\n classes: { notchedOutline: classes.inputBorder },\r\n },\r\n InputLabelProps: {\r\n className: classes.inputText,\r\n shrink: true,\r\n },\r\n inputProps: {\r\n ref: this.props.workingsRef,\r\n onPaste: (e) => {\r\n e.preventDefault();\r\n },\r\n onDrop: (e) => {\r\n e.preventDefault();\r\n },\r\n onFocus: disableArrowKeyListeners,\r\n onBlur: enableArrowKeyListeners,\r\n spellCheck: false\r\n },\r\n };\r\n\r\n if (useListener) {\r\n return (\r\n \r\n \r\n \r\n
\r\n \r\n );\r\n } else {\r\n return (\r\n \r\n \r\n
\r\n );\r\n }\r\n }\r\n}\r\n\r\n// Workings (connected to styles)\r\n// --------------------------------------------------------------------------\r\n\r\nconst styles = ({ palette, spacing }) => ({\r\n container: {\r\n boxSizing: \"border-box\",\r\n margin: spacing.unit,\r\n marginBottom: 0,\r\n flexShrink: 1,\r\n flexGrow: 1,\r\n },\r\n inputText: {\r\n color: `${palette.background.contrastText} !important`,\r\n // fontFamily: 'Courier New, Courier, monospace'\r\n },\r\n inputBorder: {\r\n borderColor: `${palette.background.contrastText} !important`,\r\n },\r\n});\r\n\r\nWorkings = withStyles(styles)(Workings);\r\n\r\n// Export\r\n// --------------------------------------------------------------------------\r\nexport { Workings };\r\n","import React from 'react';\r\nimport { withStyles } from '@material-ui/core/styles';\r\nimport { Button } from '@material-ui/core';\r\n\r\nconst styles = theme => ({\r\n button: {\r\n margin: theme.spacing.unit,\r\n height: \"32px\"\r\n },\r\n singleButton: {\r\n minWidth: 32\r\n },\r\n doubleButton: {\r\n minWidth: 64 + theme.spacing.unit*2\r\n },\r\n plusMinus: {\r\n fontFamily: 'Courier New, Courier, monospace'\r\n },\r\n squareRoot: {\r\n fontFamily: 'Courier New, Courier, monospace',\r\n } \r\n});\r\n\r\nconst CalculatorButton = ({ value, onSelect, classes }) => {\r\n if (value === \"\") {\r\n const styles = [classes.button, classes.singleButton];\r\n return
;\r\n }\r\n\r\n let display = value;\r\n const styles = [classes.button];\r\n styles.push(value === '=' ? classes.doubleButton : classes.singleButton);\r\n if (value === '@') {\r\n styles.push(classes.plusMinus);\r\n display = '\\xB1';\r\n }\r\n if (value === '<') {\r\n styles.push(classes.plusMinus);\r\n display = '\\u2190';\r\n }\r\n if (value === '/') {\r\n styles.push(classes.plusMinus);\r\n display = '\\u00F7';\r\n } \r\n if (value === '*') {\r\n styles.push(classes.plusMinus);\r\n // display = 'x';\r\n display = '\\u00d7';\r\n }\r\n if (value === '}') {\r\n styles.push(classes.squareRoot);\r\n display = '\\u221a';\r\n } \r\n \r\n return (\r\n {\r\n if (value) onSelect(value);\r\n }}\r\n >\r\n {display}\r\n \r\n );\r\n};\r\n\r\nexport default withStyles(styles)(CalculatorButton);\r\n","import React from 'react';\r\nimport CalculatorButton from './calculator-button';\r\n\r\nconst CalculatorButtonRow = ({ onSelect, buttons }) => {\r\n return (\r\n \r\n {buttons.map(button => (\r\n \r\n ))}\r\n
\r\n );\r\n};\r\n\r\nexport default CalculatorButtonRow;\r\n","import React, { Component } from 'react';\r\n\r\nimport {addCalculatorSymbols} from './calculator-helper'\r\n\r\nimport withStyles from '@material-ui/core/styles/withStyles';\r\n\r\nconst displayValue=(value)=>{\r\n if (!value) return \"\";\r\n return value.replace('/', '\\u00F7').replace('*','\\u00D7')\r\n}\r\n\r\nclass CalculatorDisplay extends Component {\r\n render() {\r\n const { value, classes } = this.props;\r\n\r\n return (\r\n \r\n {addCalculatorSymbols(value)}\r\n
);\r\n }\r\n}\r\n\r\nconst styles = ({ spacing }) => ({\r\n container: {\r\n background: 'black',\r\n fontWeight: 'bold',\r\n color: 'lime',\r\n width: `calc(100% - ${spacing.unit * 4}px)`,\r\n padding: spacing.unit,\r\n fontFamily: 'Courier New, Courier, monospace',\r\n fontSize: '16px',\r\n minHeight: '18px',\r\n margin: `${spacing.unit*1.5}px ${spacing.unit}px ${spacing.unit*.5}px ${spacing.unit}px`,\r\n borderRadius: '5px',\r\n wordBreak: 'break-all'\r\n }\r\n});\r\n\r\nCalculatorDisplay = withStyles(styles)(CalculatorDisplay);\r\n\r\nexport { CalculatorDisplay };\r\n","// npm\r\nimport React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\n// material-ui\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\n// react\r\nimport { Align } from \"components/layout/align\";\r\nimport CalculatorButtonRow from \"./calculator-button-row\";\r\nimport { CalculatorDisplay } from \"./calculator-display\";\r\nimport {\r\n isSEB,\r\n removeCalculatorSymbols,\r\n isValidPercentage,\r\n processPercentage,\r\n isValidSquareRoot,\r\n processSquareRoot,\r\n} from \"./calculator-helper\";\r\n\r\nimport check from \"check-types\";\r\n\r\nimport {\r\n isOperand,\r\n isOperator,\r\n getResult,\r\n plusMinusValue,\r\n spaceOutResult,\r\n decimalPointAllowed,\r\n operatorAllowed,\r\n} from \"./calculator-helper\";\r\n\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport {\r\n getCalculatorPercentageButton,\r\n getCalculatorSquareButton,\r\n} from \"redux/reducers/settings/client/selectors\";\r\n\r\n// constants\r\nimport { CALCULATOR_BUTTONS } from \"./constants\";\r\n\r\n// Calculator (not connected to styles)\r\n// ----------------------------------------------------------------------------\r\n\r\nclass Calculator extends Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n value: \"\",\r\n };\r\n\r\n this.justProcessedAnswer = false;\r\n this.maxLength = -1;\r\n this.handleButtonClick = this.handleButtonClick.bind(this);\r\n }\r\n\r\n handleButtonClick(_button) {\r\n const { value: _value } = this.state;\r\n const { evaluationType } = this.props;\r\n\r\n let value = removeCalculatorSymbols(_value);\r\n const button = removeCalculatorSymbols(_button);\r\n\r\n if (isOperand(button) && this.justProcessedAnswer) {\r\n value = button;\r\n this.justProcessedAnswer = false;\r\n } else if (\r\n isOperand(button) ||\r\n (isOperator(button) && operatorAllowed(button, value))\r\n ) {\r\n if (button !== \".\" || decimalPointAllowed(value)) {\r\n if (this.maxLength === -1 || value.length < this.maxLength) {\r\n value += button;\r\n this.justProcessedAnswer = false;\r\n }\r\n }\r\n } else if (button === \"=\") {\r\n const result = getResult(value, evaluationType);\r\n\r\n if (check.assigned(result)) {\r\n this.props.onResult(spaceOutResult(\"\" + value, result, button));\r\n value = \"\" + result;\r\n this.justProcessedAnswer = true;\r\n }\r\n } else if (button === \"C\") {\r\n value = \"\";\r\n this.justProcessedAnswer = false;\r\n } else if (button === \"<\") {\r\n value = value.slice(0, -1);\r\n this.justProcessedAnswer = false;\r\n } else if (\r\n button === \"@\" &&\r\n (this.maxLength === -1 || value.length < this.maxLength)\r\n ) {\r\n value = plusMinusValue(value);\r\n } else if (button === \"%\" && isValidPercentage(value)) {\r\n const result = processPercentage(value);\r\n if (result) {\r\n this.props.onResult(spaceOutResult(\"\" + value, result, button));\r\n value = \"\" + result;\r\n this.justProcessedAnswer = true;\r\n }\r\n } else if (button === \"}\" && isValidSquareRoot(value)) {\r\n const squareRootValue = processSquareRoot(value, evaluationType);\r\n\r\n if (check.assigned(squareRootValue)) {\r\n this.props.onResult(\r\n spaceOutResult(\"\" + value, squareRootValue, button)\r\n );\r\n value = \"\" + squareRootValue;\r\n this.justProcessedAnswer = true;\r\n }\r\n }\r\n\r\n this.setState({ value });\r\n }\r\n\r\n getCalculatorButtons() {\r\n return (\r\n \r\n {this.getExtraButtons()}\r\n {CALCULATOR_BUTTONS.map((buttonRow, index) => (\r\n \r\n ))}\r\n \r\n );\r\n }\r\n\r\n getExtraButtons() {\r\n const { calculatorPercentageButton, calculatorSquareButton } =\r\n this.props;\r\n\r\n if (calculatorPercentageButton || calculatorSquareButton) {\r\n let buttonRow = [];\r\n if (calculatorSquareButton) {\r\n buttonRow.unshift(\"}\");\r\n }\r\n if (calculatorPercentageButton) {\r\n buttonRow.unshift(\"%\");\r\n }\r\n const max=4-buttonRow.length;\r\n for (let i = 0; i < max; i++) {\r\n buttonRow.unshift(\"\");\r\n }\r\n\r\n return (\r\n \r\n );\r\n } else {\r\n return null;\r\n }\r\n }\r\n\r\n render() {\r\n const { classes } = this.props;\r\n const { value } = this.state;\r\n const displayProps = {\r\n value,\r\n };\r\n const className = `${classes.container} ${isSEB() && classes.seb}`;\r\n return (\r\n \r\n
\r\n \r\n {/*
{navigator.userAgent}
*/}\r\n
\r\n {this.getCalculatorButtons(this.handleButtonClick)}\r\n
\r\n \r\n
\r\n );\r\n }\r\n}\r\n\r\n// Calculator (connected to styles)\r\n// ----------------------------------------------------------------------------\r\n\r\nconst styles = ({ palette, spacing }) => ({\r\n container: {\r\n borderWidth: 1,\r\n boxSizing: \"border-box\",\r\n borderStyle: \"solid\",\r\n borderColor: palette.background.contrastText,\r\n borderRadius: 8,\r\n flexShrink: 0,\r\n margin: spacing.unit,\r\n marginTop: 0,\r\n },\r\n seb: {\r\n marginBottom: spacing.unit * 8,\r\n },\r\n});\r\n\r\nCalculator = withStyles(styles)(Calculator);\r\n\r\nconst mapStoreToProps = (store) => {\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n const calculatorPercentageButton =\r\n getCalculatorPercentageButton(clientSettingsData);\r\n const calculatorSquareButton =\r\n getCalculatorSquareButton(clientSettingsData);\r\n\r\n return {\r\n calculatorPercentageButton,\r\n calculatorSquareButton,\r\n };\r\n};\r\n\r\nCalculator = connect(mapStoreToProps)(Calculator);\r\n\r\n// EXPORT\r\n// ----------------------------------------------------------------------------\r\nexport { Calculator };\r\n","\r\nconst getWorkings = (workingsData) => workingsData.get('value');\r\n\r\nexport {getWorkings}","// npm\r\nimport React from 'react'\r\nimport {connect} from 'react-redux'\r\n\r\n// react\r\nimport {Workings} from './workings'\r\nimport {Calculator} from './calculator'\r\nimport {MaterialScrollWrapper} from 'components/layout/scroll_wrapper/material-scroll-wrapper'\r\n\r\n// redux (selectors)\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getExamData, getSettingsData} from 'redux/reducers/selectors'\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getCalculatorEvaluationMode} from 'redux/reducers/settings/client/selectors'\r\nimport {getQuestionDataById} from 'redux/reducers/exam/content/questions/selectors'\r\nimport {getWorkingsData} from 'redux/reducers/exam/content/questions/question/selectors'\r\nimport {getCurrentQuestionId, getQuestionsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getWorkings} from 'redux/reducers/exam/content/questions/question/workings/selectors'\r\n\r\n// redux (actions)\r\nimport {saveWorkings} from './actions'\r\n\r\nimport check from 'check-types';\r\nimport { addCalculatorSymbols } from './calculator-helper'\r\n\r\n\r\n// WorkingsPanel (not connected to store)\r\n// ------------------------------------------------------\r\n\r\nconst setFocusPosition=(elem,caretPos)=>{\r\n if(elem != null) {\r\n if(elem.createTextRange) {\r\n var range = elem.createTextRange();\r\n range.move('character', caretPos);\r\n range.select();\r\n }\r\n else {\r\n try{\r\n elem.focus();\r\n elem.setSelectionRange(caretPos, caretPos);\r\n }\r\n catch(e){\r\n elem.focus();\r\n }\r\n }\r\n} \r\n}\r\n\r\nclass WorkingsPanel extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n this.workingsRef = React.createRef();\r\n this.setFocus = false;\r\n this.handleWorkingsChange = this.handleWorkingsChange.bind(this);\r\n this.handleCalculatorResult = this.handleCalculatorResult.bind(this);\r\n }\r\n\r\n componentDidUpdate() {\r\n if (this.setFocus) {\r\n const workingsElement = this.workingsRef.current;\r\n if (workingsElement){\r\n if (this.cursorPosition) {\r\n setFocusPosition(workingsElement, this.cursorPosition);\r\n this.cursorPosition = null;\r\n }\r\n else workingsElement.focus();\r\n }\r\n this.setFocus = false;\r\n }\r\n }\r\n\r\n handleWorkingsChange(value, setFocus = false) {\r\n if (check.number(setFocus)){\r\n this.setFocus = true;\r\n this.cursorPosition=setFocus;\r\n }\r\n else{\r\n this.setFocus = setFocus;\r\n }\r\n this.props.onChange(value);\r\n }\r\n\r\n handleCalculatorResult(_result) {\r\n const result = addCalculatorSymbols(_result);\r\n const { value } = this.props;\r\n const workingsElement = this.workingsRef.current;\r\n const { selectionStart, selectionEnd } = workingsElement;\r\n\r\n this.cursorPosition = selectionStart + result.length + 1;\r\n\r\n const workingsBeforeSelection = value.substring(0, selectionStart);\r\n const workingsAfterSelection = value.substring(selectionEnd);\r\n const newValue = `${workingsBeforeSelection}${result}\\n${workingsAfterSelection}`;\r\n \r\n this.handleWorkingsChange(newValue, true);\r\n }\r\n\r\n render() {\r\n const workingsProps = {\r\n value: this.props.value,\r\n workingsRef: this.workingsRef,\r\n onChange: this.handleWorkingsChange\r\n };\r\n\r\n const wrapperDivStyle = {\r\n display: 'flex',\r\n width: 220,\r\n height: '100%',\r\n boxSizing: 'border-box',\r\n flexDirection: 'column'\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n {this.Calculator}\r\n
\r\n \r\n );\r\n }\r\n\r\n get Calculator() {\r\n if (this.props.disableCalculator) {\r\n return null;\r\n }\r\n \r\n let {evaluationType} = this.props;\r\n if (!check.assigned(evaluationType)) evaluationType = 1;\r\n\r\n const props = {\r\n onResult: this.handleCalculatorResult, \r\n evaluationType\r\n }\r\n return ;\r\n }\r\n}\r\n\r\n// WorkingsPanel (connected to store)\r\n// ------------------------------------------------------\r\n\r\nconst mapStoreToProps = store => {\r\n const contentData = getContentData(getExamData(store));\r\n const questionsData = getQuestionsData(contentData);\r\n const currentQuestionId = getCurrentQuestionId(contentData);\r\n const questionData = getQuestionDataById(questionsData)(currentQuestionId);\r\n\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n return {\r\n value: getWorkings(getWorkingsData(questionData)),\r\n evaluationType: getCalculatorEvaluationMode(clientSettingsData),\r\n currentQuestionId\r\n };\r\n};\r\n\r\nconst mapDispatchToProps = dispatch => ({\r\n onChange: questionId => value => {\r\n dispatch(saveWorkings(questionId, value));\r\n }\r\n});\r\n\r\nconst mergeProps = ({value, currentQuestionId, evaluationType}, {onChange}, {disableCalculator}) => ({\r\n onChange: onChange(currentQuestionId),\r\n disableCalculator,\r\n evaluationType,\r\n value\r\n});\r\n\r\nconst args = [mapStoreToProps, mapDispatchToProps, mergeProps];\r\nWorkingsPanel = connect(...args)(WorkingsPanel);\r\n\r\n\r\n// Export\r\n// ------------------------------------------------------\r\nexport {WorkingsPanel}","\r\n// redux (action-types)\r\nimport {CHANGE} from 'redux/reducers/exam/content/questions/question/workings/action-types'\r\n\r\n\r\nconst saveWorkings = (questionId, value) => ({type: CHANGE, questionId, value});\r\n\r\n\r\nexport {saveWorkings}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\nimport NavigateNextIcon from '@material-ui/icons/NavigateNext'\r\nimport NavigateBeforeIcon from '@material-ui/icons/NavigateBefore'\r\n\r\n// react\r\nimport {MaterialText} from 'components/presentation/material-text'\r\nimport {MobileActionButton} from 'components/layout/action_button/mobile'\r\nimport {activityLogger, ACTIVITIES} from 'libs/activity_logger/activity-logger'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getParentSectionId} from 'redux/reducers/exam/content/mappings/selectors'\r\nimport {getNumber} from 'redux/reducers/exam/content/questions/question/selectors'\r\nimport {getQuestionDataById, getQuestionCount} from 'redux/reducers/exam/content/questions/selectors'\r\nimport {getQuestionsData, getMappingsData, getCurrentQuestionId} from 'redux/reducers/exam/content/selectors'\r\nimport {getNextQuestionId, getQuestionNoInSection, getPaperPartId, getQuestionCountInSection, getPreviousQuestionId} from 'redux/reducers/exam/content/complex-selectors'\r\n\r\n// utils\r\nimport {keyListener, KEYS} from 'utils/key-listener'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\n// MiniQuestionNavigation (not connected to store)\r\n// -----------------------------------------------------------------------\r\n\r\nclass MiniQuestionNavigation extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tnavigate(direction)\r\n\t{\r\n\t\tdirection = direction === 1 ? 'next' : 'previous';\r\n\t\tconst nextQuestionId = this.props[`${direction}Question`].id;\r\n\r\n\t\tif (nextQuestionId) {\r\n\t\t\tconst activity = direction === 'next' ? ACTIVITIES.MOVE_NEXT_QUESTION : ACTIVITIES.MOVE_PREVIOUS_QUESTION;\r\n\t\t\tactivityLogger.log(activity, {nextQuestionId});\r\n\t\t\tthis.props.onQuestionChange(nextQuestionId);\r\n\t\t}\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.navigateLeft = this.navigate.bind(this, -1);\r\n\t\tthis.navigateRight = this.navigate.bind(this, 1);\r\n\t}\r\n\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tthis.unsubscribeFromLeftArrowKey = keyListener.subscribe(KEYS.LEFT_ARROW, this.navigateLeft);\r\n\t\tthis.unsubscribeFromRightArrowKey = keyListener.subscribe(KEYS.RIGHT_ARROW, this.navigateRight);\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tthis.unsubscribeFromLeftArrowKey();\r\n\t\tthis.unsubscribeFromRightArrowKey();\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.getQuestionButton(-1)}\r\n\t\t\t\t{this.QuestionProgress}\r\n\t\t\t\t{this.getQuestionButton(1)}\r\n\t\t\t
\r\n\t\t)\r\n\t}\r\n\r\n\tgetQuestionButton(direction)\r\n\t{\r\n\t\tconst {previousQuestion, nextQuestion, useDesktopButtons} = this.props;\r\n\r\n\t\tconst Icon = direction === -1 ? NavigateBeforeIcon : NavigateNextIcon;\r\n\t\tconst onClick = direction === -1 ? this.navigateLeft : this.navigateRight;\r\n\t\tconst disabled = direction === -1 ? !previousQuestion.id : !nextQuestion.id;\r\n\r\n\t\tconst {GENERAL:{PREVIOUS, NEXT}} = MESSAGE_IDS;\r\n\t\tconst text = this.props.messages[direction === -1 ? PREVIOUS : NEXT];\r\n\r\n\t\tconst buttonProps = {Icon, onClick, disabled};\r\n\t\treturn {text} ;\r\n\t}\r\n\r\n\tget QuestionProgress()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{`${this.CurrentQuestionNumber} / ${this.props.questionCount}`}\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tget CurrentQuestionNumber()\r\n\t{\r\n\t\tconst {previousQuestion, nextQuestion} = this.props;\r\n\t\treturn previousQuestion.number ? previousQuestion.number + 1 :\r\n\t\t\tnextQuestion.number ? nextQuestion.number - 1 : 1;\r\n\t}\r\n}\r\n\r\nMiniQuestionNavigation.propTypes = {\r\n\tpreviousQuestion: PropTypes.shape({\r\n\t\tid: PropTypes.number,\r\n\t\tnumber: PropTypes.number\r\n\t}).isRequired,\r\n\tnextQuestion: PropTypes.shape({\r\n\t\tid: PropTypes.number,\r\n\t\tnumber: PropTypes.number\r\n\t}).isRequired,\r\n\tquestionCount: PropTypes.number.isRequired,\r\n\tonQuestionChange: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// MiniQuestionNavigation (connected to store)\r\n// -----------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\tconst contentData = getContentData(getExamData(store));\r\n\tconst questionsData = getQuestionsData(contentData);\r\n\tconst mappingsData = getMappingsData(contentData);\r\n\r\n\tconst currentQuestionId = getCurrentQuestionId(contentData);\r\n\tconst currentPaperPartId = getPaperPartId(contentData)(currentQuestionId);\r\n\r\n\t// const questionCount = currentPaperPartId ?\r\n\t// \tgetQuestionCountInSection(contentData)(currentPaperPartId) :\r\n\t// \tgetQuestionCount(questionsData);\r\n\r\n\tconst questionCount = getQuestionCount(questionsData);\r\n\r\n\tconst _getQuestionDataById = getQuestionDataById(questionsData);\r\n\r\n\tconst nextQuestionId = getNextQuestionId(contentData);\r\n\tconst nextQuestionData = _getQuestionDataById(nextQuestionId);\r\n\t//const nextQuestionNumber = !nextQuestionData ? undefined : currentPaperPartId ? getQuestionNoInSection(contentData)(currentPaperPartId, nextQuestionId) : getNumber(nextQuestionData);\r\n\tconst nextQuestionNumber = !nextQuestionData ? undefined : getNumber(nextQuestionData);\r\n\tconst nextQuestion = {id: nextQuestionId, number: nextQuestionNumber};\r\n\r\n\tconst previousQuestionId = getPreviousQuestionId(contentData);\r\n\tconst previousQuestionData = _getQuestionDataById(previousQuestionId);\r\n\t//const previousQuestionNumber = !previousQuestionData ? undefined : currentPaperPartId ? getQuestionNoInSection(contentData)(currentPaperPartId, previousQuestionId) : getNumber(previousQuestionData);\r\n\tconst previousQuestionNumber = !previousQuestionData ? undefined : getNumber(previousQuestionData);\r\n\tconst previousQuestion = {id: previousQuestionId, number: previousQuestionNumber};\r\n\r\n\treturn {questionCount, previousQuestion, nextQuestion};\r\n}\r\n\r\nMiniQuestionNavigation = connect(mapStoreToProps)(MiniQuestionNavigation);\r\nMiniQuestionNavigation = withMessages(MiniQuestionNavigation);\r\n\r\nMiniQuestionNavigation.propTypes = {\r\n\tonQuestionChange: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// MiniQuestionNavigation (connected to styles)\r\n// -----------------------------------------------------------------------\r\n\r\nconst styles = ({palette}) => ({\r\n\tcontainer: {\r\n\t\tflexShrink: 0,\r\n\t\tdisplay: 'flex',\r\n\t\talignItems: 'center'\r\n\t},\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText\r\n\t}\r\n});\r\n\r\nMiniQuestionNavigation = withStyles(styles)(MiniQuestionNavigation);\r\n\r\n\r\n// Export\r\n// -----------------------------------------------------------------------\r\nexport {MiniQuestionNavigation}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport AssignmentTurnedInIcon from '@material-ui/icons/AssignmentTurnedIn'\r\n\r\n// react\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {ActionButton} from 'components/layout/action_button/button'\r\n\r\n// other\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst finishMessageId = MESSAGE_IDS.GENERAL.FINISH;\r\n\r\n\r\nlet FinishExamButton = ({onClick, messages}) => (\r\n\t\r\n\t\t{messages[finishMessageId]}\r\n\t \r\n)\r\n\r\nFinishExamButton.propTypes = {\r\n\tonClick: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[finishMessageId]: PropTypes.string.isRequired\r\n\t})\r\n}\r\n\r\n\r\nFinishExamButton = withMessages(FinishExamButton);\r\n\r\n\r\nexport {FinishExamButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport ArrowForwardIcon from '@material-ui/icons/ArrowForward'\r\n\r\n// react\r\nimport {ActionButton} from 'components/layout/action_button/button'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nlet StartAnsweringButton = ({onClick, messages}) => (\r\n\t\r\n\t\t{messages[MESSAGE_IDS.EXAM.START_ANSWERING]}\r\n\t \r\n)\r\n\r\nStartAnsweringButton.propTypes = {\r\n\tonClick: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nStartAnsweringButton = withMessages(StartAnsweringButton);\r\n\r\n\r\nexport {StartAnsweringButton}","\r\nconst SCALE = 96 / 72;\r\n\r\n\r\nexport {SCALE}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {Document, Page} from 'react-pdf'\r\n\r\n// constants\r\nimport {SCALE} from './constants'\r\n\r\n\r\nclass DocumentLoader extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.state = {pageCount: null, pageToLoad: 1, maxPageWidth: null};\r\n\r\n\t\tthis.onDocumentLoad = ({numPages}) => {\r\n\t\t\tthis.setState({pageCount: numPages});\r\n\t\t}\r\n\r\n\t\tconst createOnPageLoad = () => {\r\n\t\t\tlet maxPageWidth = 0;\r\n\r\n\t\t\tconst updateMaxPageWidth = (width) => {\r\n\t\t\t\tif (width > maxPageWidth) { maxPageWidth = width; }\r\n\t\t\t}\r\n\r\n\t\t\tconst updateLoaderState = () => {\r\n\t\t\t\tlet {pageToLoad, pageCount} = this.state;\r\n\t\t\t\tconst newState = pageToLoad < pageCount ?\r\n\t\t\t\t\t{pageToLoad: pageToLoad + 1} : {maxPageWidth};\r\n\t\t\t\tthis.setState(newState);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn ({width}) => {\r\n\t\t\t\tupdateMaxPageWidth(width * SCALE);\r\n\t\t\t\tupdateLoaderState();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// IIFE to restrict scope of maxPageWidth\r\n\t\tthis.onPageLoad = createOnPageLoad();\r\n\t}\r\n\r\n\tcomponentDidUpdate()\r\n\t{\r\n\t\tconst {pageCount, maxPageWidth} = this.state;\r\n\r\n\t\tif (pageCount && maxPageWidth) {\r\n\t\t\tthis.props.onLoad({pageCount, maxPageWidth});\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tconst documentProps = {\r\n\t\t\tfile: this.props.resourceUrl,\r\n\t\t\tonLoadSuccess: this.onDocumentLoad\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.DocumentPage}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget DocumentPage()\r\n\t{\r\n\t\tconst pageProps = {\r\n\t\t\tonLoadSuccess: this.onPageLoad,\r\n\t\t\tpageNumber: this.state.pageToLoad\r\n\t\t}\r\n\r\n\t\treturn \r\n\t}\r\n}\r\n\r\nDocumentLoader.propTypes = {\r\n\tresourceUrl: PropTypes.string.isRequired,\r\n\tonLoad: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nexport {DocumentLoader}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {Document, Page} from 'react-pdf'\r\n\r\n// material-ui\r\nimport IconButton from '@material-ui/core/IconButton'\r\nimport MobileStepper from '@material-ui/core/MobileStepper'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\nimport CircularProgress from '@material-ui/core/CircularProgress'\r\nimport KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'\r\nimport KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'\r\n\r\n// react\r\nimport {DocumentLoader} from './document-loader'\r\nimport {ActionButton} from 'components/layout/action_button/button'\r\nimport {MaterialScrollWrapper} from 'components/layout/scroll_wrapper/material-scroll-wrapper'\r\n\r\n// constants\r\nimport {SCALE} from './constants'\r\nimport {contentBarHeight} from '../../../constants'\r\n\r\n\r\n// PdfResourcePanel (not connected to styles)\r\n// -------------------------------------------------\r\n\r\nclass PdfResourcePanel extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {\r\n\t\t\tloaded: false,\r\n\t\t\tpageCount: null,\r\n\t\t\tpageNumber: 1,\r\n\t\t\tmaxPageWidth: 0\r\n\t\t}\r\n\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tnavigate(direction)\r\n\t{\r\n\t\tthis.setState(prevState => ({\r\n\t\t\tpageNumber: prevState.pageNumber + direction\r\n\t\t}));\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.onDocumentLoad = ({pageCount, maxPageWidth}) => {\r\n\t\t\tthis.setState({pageCount, maxPageWidth, loaded: true});\r\n\t\t}\r\n\r\n\t\tthis.navigateBackward = this.navigate.bind(this, -1);\r\n\t\tthis.navigateForward = this.navigate.bind(this, 1);\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tconst {props:{classes}, state:{maxPageWidth}} = this;\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.state.loaded ? this.LoadedDocument : this.DocumentLoader}\r\n\t\t\t
\r\n\t\t);\r\n\t}\r\n\r\n\tget DocumentLoader()\r\n\t{\r\n\t\tconst loaderProps = {\r\n\t\t\tresourceUrl: this.props.resourceUrl,\r\n\t\t\tonLoad: this.onDocumentLoad\r\n\t\t}\r\n\r\n\t\treturn ;\r\n\t}\r\n\r\n\tget LoadedDocument()\r\n\t{\r\n\t\tconst {classes} = this.props;\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\t{this.MobileStepper}\r\n\t\t\t\t
\r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t{this.Document}\r\n\t\t\t\t\t
\r\n\t\t\t\t \r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget MobileStepper()\r\n\t{\r\n\t\tconst {classes} = this.props;\r\n\t\tconst variant = this.state.pageCount > 10 ? 'progress': 'dots';\r\n\t\tconst props = {\r\n\t\t\tvariant,\r\n\t\t\tposition: 'static',\r\n\t\t\tsteps: this.state.pageCount,\r\n\t\t\tclassName: classes.stepper,\r\n\t\t\tclasses: {dot: classes.dot, dotActive: classes.dotActive},\r\n\t\t\tactiveStep: this.state.pageNumber - 1,\r\n\t\t\tbackButton: this.getStepperButton(-1),\r\n\t\t\tnextButton: this.getStepperButton(1)\r\n\t\t}\r\n\r\n\t\treturn ;\r\n\t}\r\n\r\n\tgetStepperButton(direction=1)\r\n\t{\r\n\t\tconst Icon = direction === -1 ? KeyboardArrowLeft : KeyboardArrowRight;\r\n\t\tconst onClick = direction === -1 ? this.navigateBackward : this.navigateForward;\r\n\t\tconst disabled = direction === -1 ? this.isFirstPage : this.isLastPage;\r\n\t\tconst alignment = direction === -1 ? 'left' : 'right';\r\n\r\n\t\tconst props = {Icon, onClick, disabled, alignment, color: 'secondary', size: 'small'};\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{disabled ? \"\" : `Page ${this.state.pageNumber + direction}`}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget Document()\r\n\t{\r\n\t\treturn (\r\n\t\t\t e.preventDefault()}>\r\n\t\t\t\t\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget SpinnerNode()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t
\r\n\t\t);\r\n\t}\r\n\r\n\tget isFirstPage()\r\n\t{\r\n\t\treturn this.state.pageNumber === 1;\r\n\t}\r\n\r\n\tget isLastPage()\r\n\t{\r\n\t\treturn this.state.pageNumber === this.state.pageCount;\r\n\t}\r\n}\r\n\r\nPdfResourcePanel.propTypes = {\r\n\tclasses: PropTypes.object.isRequired,\r\n\tresourceUrl: PropTypes.string.isRequired\r\n}\r\n\r\n\r\n// PdfResourcePanel (connected to styles)\r\n// -------------------------------------------------\r\n\r\nconst styles = ({palette}) => ({\r\n\twrapper: {\r\n\t\tdisplay: 'flex',\r\n\t\tflexDirection: 'column',\r\n\t\talignItems: 'stretch',\r\n\t\theight: 'calc(100vh - 50px)'\r\n\t},\r\n\tnavigationContainer: {\r\n\t\tflexGrow: 0,\r\n\t\tflexShrink: 0\r\n\t},\r\n\tdocumentContainer: {\r\n\t\tflexShrink: 1\r\n\t},\r\n\tstepper: {\r\n\t\tpaddingTop: 0,\r\n\t\tpaddingBottom: 0,\r\n\t\theight: contentBarHeight,\r\n\t\tbackgroundColor: palette.background.light\r\n\t},\r\n\tloading: {\r\n\t\ttextAlign: 'center'\r\n\t},\r\n\tprogress: {\r\n\t\tmarginTop: 50\r\n\t},\r\n\tdot: {\r\n\t\tbackgroundColor: palette.secondary.dark\r\n\t},\r\n\tdotActive: {\r\n\t\tbackgroundColor: palette.secondary.light\r\n\t}\r\n});\r\n\r\nPdfResourcePanel = withStyles(styles)(PdfResourcePanel);\r\n\r\n\r\n// Export\r\n// -------------------------------------------------\r\nexport {PdfResourcePanel}","import React from 'react';\r\nimport PDFJSBackend from \"./pdf-js-backend\";\r\n\r\nclass PdfJsPanel extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.viewerRef = React.createRef();\r\n this.backend = new PDFJSBackend();\r\n }\r\n\r\n componentDidMount() {\r\n const { resourceUrl } = this.props;\r\n const element = this.viewerRef.current;\r\n\r\n this.backend.init(resourceUrl, element);\r\n }\r\n \r\n\r\n render() {\r\n const { maxPageWidth } = this.props;\r\n\r\n return (\r\n \r\n\r\n
\r\n )\r\n }\r\n}\r\n\r\nexport {PdfJsPanel}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {Document, Page} from 'react-pdf'\r\nimport {PdfJsPanel as PdfJsViewer} from './pdf_js_viewer/pdf-js-panel'\r\n\r\n// material-ui\r\nimport IconButton from '@material-ui/core/IconButton'\r\nimport MobileStepper from '@material-ui/core/MobileStepper'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\nimport CircularProgress from '@material-ui/core/CircularProgress'\r\nimport KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'\r\nimport KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'\r\n\r\n// react\r\nimport {DocumentLoader} from './document-loader'\r\nimport {ActionButton} from 'components/layout/action_button/button'\r\nimport {MaterialScrollWrapper} from 'components/layout/scroll_wrapper/material-scroll-wrapper'\r\n\r\n// constants\r\nimport {SCALE} from './constants'\r\nimport {contentBarHeight} from '../../../constants'\r\n\r\n\r\n// PdfResourcePanel (not connected to styles)\r\n// -------------------------------------------------\r\n\r\nclass PdfResourcePanel extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {\r\n\t\t\tloaded: false,\r\n\t\t\tpageCount: null,\r\n\t\t\tpageNumber: 1,\r\n\t\t\tmaxPageWidth: 0\r\n\t\t}\r\n\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tnavigate(direction)\r\n\t{\r\n\t\tthis.setState(prevState => ({\r\n\t\t\tpageNumber: prevState.pageNumber + direction\r\n\t\t}));\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.onDocumentLoad = ({pageCount, maxPageWidth}) => {\r\n\t\t\tthis.setState({pageCount, maxPageWidth, loaded: true});\r\n\t\t}\r\n\r\n\t\tthis.navigateBackward = this.navigate.bind(this, -1);\r\n\t\tthis.navigateForward = this.navigate.bind(this, 1);\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tconst {props:{classes}, state:{maxPageWidth}} = this;\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.state.loaded ? this.LoadedDocument : this.DocumentLoader}\r\n\t\t\t
\r\n\t\t);\r\n\t}\r\n\r\n\tget DocumentLoader()\r\n\t{\r\n\t\tconst loaderProps = {\r\n\t\t\tresourceUrl: this.props.resourceUrl,\r\n\t\t\tonLoad: this.onDocumentLoad\r\n\t\t}\r\n\r\n\t\treturn ;\r\n\t}\r\n\r\n\tget LoadedDocument()\r\n\t{\r\n\t\tconst {classes, resourceUrl} = this.props;\r\n\t\tconst {maxPageWidth} = this.state;\r\n\t\tconst pdfProps={maxPageWidth, resourceUrl}\r\n\r\n\t\treturn \r\n\r\n\t\t// return (\r\n\t\t// \t\r\n\t\t// \t\t\r\n\t\t// \t\t\t{this.MobileStepper}\r\n\t\t// \t\t
\r\n\t\t// \t\t\r\n\t\t// \t\t\t\r\n\t\t// \t\t\t\t{this.Document}\r\n\t\t// \t\t\t
\r\n\t\t// \t\t \r\n\t\t// \t \r\n\t\t// );\r\n\t}\r\n\r\n\tget MobileStepper()\r\n\t{\r\n\t\tconst {classes} = this.props;\r\n\t\tconst variant = this.state.pageCount > 10 ? 'progress': 'dots';\r\n\t\tconst props = {\r\n\t\t\tvariant,\r\n\t\t\tposition: 'static',\r\n\t\t\tsteps: this.state.pageCount,\r\n\t\t\tclassName: classes.stepper,\r\n\t\t\tclasses: {dot: classes.dot, dotActive: classes.dotActive},\r\n\t\t\tactiveStep: this.state.pageNumber - 1,\r\n\t\t\tbackButton: this.getStepperButton(-1),\r\n\t\t\tnextButton: this.getStepperButton(1)\r\n\t\t}\r\n\r\n\t\treturn ;\r\n\t}\r\n\r\n\tgetStepperButton(direction=1)\r\n\t{\r\n\t\tconst Icon = direction === -1 ? KeyboardArrowLeft : KeyboardArrowRight;\r\n\t\tconst onClick = direction === -1 ? this.navigateBackward : this.navigateForward;\r\n\t\tconst disabled = direction === -1 ? this.isFirstPage : this.isLastPage;\r\n\t\tconst alignment = direction === -1 ? 'left' : 'right';\r\n\r\n\t\tconst props = {Icon, onClick, disabled, alignment, color: 'secondary', size: 'small'};\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{disabled ? \"\" : `Page ${this.state.pageNumber + direction}`}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget Document()\r\n\t{\r\n\t\treturn (\r\n\t\t\t e.preventDefault()}>\r\n\t\t\t\t\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget SpinnerNode()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t
\r\n\t\t);\r\n\t}\r\n\r\n\tget isFirstPage()\r\n\t{\r\n\t\treturn this.state.pageNumber === 1;\r\n\t}\r\n\r\n\tget isLastPage()\r\n\t{\r\n\t\treturn this.state.pageNumber === this.state.pageCount;\r\n\t}\r\n}\r\n\r\nPdfResourcePanel.propTypes = {\r\n\tclasses: PropTypes.object.isRequired,\r\n\tresourceUrl: PropTypes.string.isRequired\r\n}\r\n\r\n\r\n// PdfResourcePanel (connected to styles)\r\n// -------------------------------------------------\r\n\r\nconst styles = ({palette}) => ({\r\n\twrapper: {\r\n\t\tdisplay: 'flex',\r\n\t\tflexDirection: 'column',\r\n\t\talignItems: 'stretch',\r\n\t\theight: 'calc(100vh - 50px)'\r\n\t},\r\n\tnavigationContainer: {\r\n\t\tflexGrow: 0,\r\n\t\tflexShrink: 0\r\n\t},\r\n\tdocumentContainer: {\r\n\t\tflexShrink: 1\r\n\t},\r\n\tstepper: {\r\n\t\tpaddingTop: 0,\r\n\t\tpaddingBottom: 0,\r\n\t\theight: contentBarHeight,\r\n\t\tbackgroundColor: palette.background.light\r\n\t},\r\n\tloading: {\r\n\t\ttextAlign: 'center'\r\n\t},\r\n\tprogress: {\r\n\t\tmarginTop: 50\r\n\t},\r\n\tdot: {\r\n\t\tbackgroundColor: palette.secondary.dark\r\n\t},\r\n\tdotActive: {\r\n\t\tbackgroundColor: palette.secondary.light\r\n\t}\r\n});\r\n\r\nPdfResourcePanel = withStyles(styles)(PdfResourcePanel);\r\n\r\n\r\n// Export\r\n// -------------------------------------------------\r\nexport {PdfResourcePanel}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// react\r\nimport {PdfResourcePanel as PdfResourcePanelOld} from './panel'\r\nimport {PdfResourcePanel as PdfResourcePanelNew} from './panel-pdf-js'\r\n\r\n// redux (selectors)\r\nimport {getCacheData} from 'redux/reducers/selectors'\r\nimport {getCachedPdfData} from 'redux/reducers/cache/selectors'\r\nimport {getCachedUrl} from 'redux/reducers/cache/pdfs/selectors'\r\nimport { isSEB } from '../workings/calculator-helper'\r\nimport { useOldPdfViewer } from 'components/pages/_exam/initializing/checking_resources/pdf_check/pdf-browser-support';\r\n\r\n\r\n// CachedPdfResourcePanel (not connected)\r\n// -------------------------------------------------------------------------\r\n\r\nlet CachedPdfResourcePanel = ({cachedPdfData, resourceUrl}) =>\r\n{\r\n\tconst cachedUrl = getCachedUrl(cachedPdfData, resourceUrl);\r\n\tconst finalResourceUrl = cachedUrl || resourceUrl;\r\n\r\n\tif (useOldPdfViewer()){\r\n\t\treturn ;\r\n\t}\r\n\telse{\r\n\t\treturn ;\t\r\n\t}\r\n\t\r\n\t// return isSEB()?:;\r\n\t//return \r\n}\r\n\r\nCachedPdfResourcePanel.propTypes = {\r\n\tcachedPdfData: PropTypes.object.isRequired,\r\n\tresourceUrl: PropTypes.string.isRequired\r\n}\r\n\r\n\r\n// CachedPdfResourcePanel (connected)\r\n// -------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\tcachedPdfData: getCachedPdfData(getCacheData(store))\r\n});\r\n\r\nCachedPdfResourcePanel = connect(mapStoreToProps)(CachedPdfResourcePanel);\r\n\r\n\r\n// CachedPdfResourcePanel (export)\r\n// -------------------------------------------------------------------------\r\n\r\nexport {CachedPdfResourcePanel}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// material-ui\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {Align} from 'components/layout/align'\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nlet NavigationPanelTitle = ({classes, messages}) => (\r\n\t\r\n\t\t\r\n\t\t\t{messages[MESSAGE_IDS.EXAM.QUESTION_NAV_TITLE]}\r\n\t\t \r\n\t \r\n)\r\n\r\n\r\nconst styles = ({palette}) => ({\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText\r\n\t}\r\n})\r\n\r\nNavigationPanelTitle = withStyles(styles)(NavigationPanelTitle);\r\nNavigationPanelTitle = withMessages(NavigationPanelTitle);\r\n\r\n\r\nexport {NavigationPanelTitle}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport ImmutablePropTypes from 'immutable-prop-types'\r\n\r\n// material-ui\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {Align} from 'components/layout/align'\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// redux (selectors)\r\nimport {getSectionName, getMarks} from 'redux/reducers/exam/content/sections/section/selectors'\r\n\r\n// utils\r\nimport {pluralize} from 'custom/string-helper'\r\n\r\n\r\n// SectionHeading (not connected)\r\n// --------------------------------------------------------------\r\n\r\nconst generateMarksText = (marks) => (\r\n\t` (${marks} mark${marks === 1 ? '' : 's'})`\r\n)\r\n\r\nlet SectionHeading = ({sectionData, classes}) =>\r\n{\r\n\tconst name = getSectionName(sectionData);\r\n\tconst marks = getMarks(sectionData);\r\n\tconst marksText = isNaN(marks)?'':` (${pluralize('mark', marks)})`;\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t
\r\n\t\t\t\t\r\n\t\t\t\t\t{`${name}${marksText}`}\r\n\t\t\t\t \r\n\t\t\t \r\n\t\t
\r\n\t)\r\n}\r\n\r\nSectionHeading.propTypes = {\r\n\tsectionData: ImmutablePropTypes.map,\r\n\tclasses: PropTypes.object.isRequired\r\n}\r\n\r\n\r\n// SectionHeading (not connected)\r\n// --------------------------------------------------------------\r\n\r\nconst styles = ({palette}) => ({\r\n\ttext: {color: palette.background.contrastText}\r\n});\r\n\r\nSectionHeading = withStyles(styles)(SectionHeading);\r\n\r\n\r\n// SectionHeading (EXPORT)\r\n// --------------------------------------------------------------\r\nexport {SectionHeading}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\nimport MaterialBookmarkIcon from '@material-ui/icons/Bookmark'\r\n\r\n\r\n// BookmarkIcon (not connected to styles)\r\n// -------------------------------------------------\r\n\r\nlet BookmarkIcon = ({active, classes}) => {\r\n\tif (!active) { return null; }\r\n\t\r\n\tconst props = {\r\n\t\tfontSize: 'small',\r\n\t\tcolor: 'secondary',\r\n\t\tclassName: classes.icon\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nBookmarkIcon.propTypes = {\r\n\tactive: PropTypes.bool,\r\n\tclasses: PropTypes.object.isRequired\r\n}\r\n\r\n\r\n// BookmarkIcon (connected to styles)\r\n// -------------------------------------------------\r\n\r\nconst styles = (theme) => ({\r\n\ticon: {\r\n\t\tmarginTop: -14,\r\n\t\tmarginLeft: 18,\r\n\t\tposition: 'absolute',\r\n\t\tcolor: theme.palette.secondary.main\r\n\t}\r\n});\r\n\r\nBookmarkIcon = withStyles(styles)(BookmarkIcon);\r\n\r\n\r\n// Export\r\n// -------------------------------------------------\r\nexport {BookmarkIcon}","\r\n// redux (action-types)\r\nimport {SET_CURRENT_QUESTION_ID} from 'redux/reducers/exam/content/action-types'\r\n\r\n\r\nconst setCurrentQuestionId = (value) => ({\r\n\ttype: SET_CURRENT_QUESTION_ID,\r\n\tvalue\r\n});\r\n\r\n\r\nexport {setCurrentQuestionId}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport Fab from '@material-ui/core/Fab'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {BookmarkIcon} from './bookmark-icon'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getParentSectionId} from 'redux/reducers/exam/content/mappings/selectors'\r\nimport {getCurrentQuestionId, getMappingsData} from 'redux/reducers/exam/content/selectors'\r\nimport {hasSamePaperPart, getQuestionNoInSection, getPaperPartId} from 'redux/reducers/exam/content/complex-selectors'\r\nimport {getId, getNumber, isBookmarked, isAnswered, isDisabled} from 'redux/reducers/exam/content/questions/question/selectors'\r\n\r\n// redux (actions)\r\nimport {setCurrentQuestionId} from './actions'\r\n\r\n\r\n\r\n// QuestionButton (not connected to store)\r\n// ----------------------------------------------------------------------------\r\n\r\nlet QuestionButton = (props) =>\r\n{\r\n\tconst classes = {\r\n\t\tlabel: props.selected ? props.classes.activeLabel : undefined,\r\n\t\tdisabled: props.disabled ? props.classes.buttonDisabledColor : \r\n\t\t\t\t\t\t props.selected ? props.classes.buttonActiveColor : props.classes.buttonColor\r\n\t}\r\n\r\n\tconst fabProps = {\r\n\t\tsize: 'small',\r\n\t\tonClick: props.onClick,\r\n\t\tdisabled: props.disabled,\r\n\t\tclassName: props.classes.button,\r\n\t\tcolor: props.answered ? 'primary' : 'default',\r\n\t\tclasses: !props.selected ? {} : {label: props.classes.activeLabel}\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{props.number}\r\n\t\t\t\t\r\n\t\t\t \r\n\t\t
\r\n\t);\r\n}\r\n\r\nQuestionButton.propTypes = {\r\n\tclasses: PropTypes.object.isRequired,\r\n\tnumber: PropTypes.number.isRequired,\r\n\tonClick: PropTypes.func.isRequired,\r\n\tbookmarked: PropTypes.bool.isRequired,\r\n\tanswered: PropTypes.bool.isRequired,\r\n\tselected: PropTypes.bool.isRequired,\r\n\tdisabled: PropTypes.bool.isRequired\r\n}\r\n\r\n\r\n// QuestionButton (connected to styles)\r\n// ----------------------------------------------------------------------------\r\n\r\nconst styles = ({spacing, palette}) => ({\r\n\tactiveLabel: {\r\n\t\ttextDecoration: 'underline'\r\n\t},\r\n\tbutton: {\r\n\t\tmargin: spacing.unit / 2,\r\n\t\tpadding: 0,\r\n\t},\r\n\tbuttonColor: {\r\n\t\tbackgroundColor: palette.primary.main + \"!important\",\r\n\t\tcolor: palette.primary.contrastText + \"!important\"\r\n\t},\r\n\tbuttonActiveColor: {\r\n\t\tbackgroundColor: palette.primary.dark + \"!important\",\r\n\t\tcolor: palette.primary.contrastText + \"!important\"\r\n\t},\r\n\tbuttonDisabledColor: {\r\n\t\tbackgroundColor: palette.primary.light + \"!important\",\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t}\r\n});\r\n\r\nQuestionButton = withStyles(styles)(QuestionButton);\r\n\r\n\r\n// QuestionButton (connected to store)\r\n// ----------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n\tconst contentData = getContentData(getExamData(store));\r\n\tconst currentQuestionId = getCurrentQuestionId(contentData);\r\n\r\n\treturn {\r\n\t\tpaperPartId: getPaperPartId(contentData)(currentQuestionId),\r\n\t\tcurrentQuestionId,\r\n\t\tcontentData\r\n\t}\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({dispatch});\r\n\r\nconst mergeProps = ({currentQuestionId, paperPartId, contentData}, {dispatch}, {questionData, onClick}) => ({\r\n\tselected: getId(questionData) === currentQuestionId,\r\n\tbookmarked: isBookmarked(questionData),\r\n\tanswered: isAnswered(questionData),\r\n\tdisabled: isDisabled(questionData) || !hasSamePaperPart(contentData)(currentQuestionId, getId(questionData)),\r\n\t// number: paperPartId ? getQuestionNoInSection(contentData)(paperPartId, getId(questionData)) : getNumber(questionData),\r\n\tnumber: getNumber(questionData),\r\n\tonClick\r\n})\r\n\r\nconst args = [mapStoreToProps, mapDispatchToProps, mergeProps];\r\nQuestionButton = connect(...args)(QuestionButton);\r\n\r\n\r\n// Export\r\n// ----------------------------------------------------------------------------\r\nexport {QuestionButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n\r\nconst divStyle = {\r\n\tdisplay: 'flex',\r\n\tflexWrap: 'wrap'\r\n}\r\n\r\n\r\nconst ResultPanel = ({children}) => (\r\n\t\r\n\t\t{children}\r\n\t
\r\n);\r\n\r\nResultPanel.propTypes = {\r\n\tchildren: PropTypes.node.isRequired\r\n}\r\n\r\n\r\nexport {ResultPanel}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Card from '@material-ui/core/Card'\r\nimport CardContent from '@material-ui/core/CardContent'\r\n\r\n\r\nconst cardStyle = {\r\n\tbackgroundColor: 'transparent',\r\n\tpadding:'0px'\r\n}\r\n\r\n\r\nconst SectionPanel = ({headingNode, resultPanel}) => (\r\n\t\r\n\t\t\r\n\t\t\t{headingNode}\r\n\t\t\t{resultPanel}\r\n\t\t \r\n\t \r\n)\r\n\r\nSectionPanel.propTypes = {\r\n\theadingNode: PropTypes.node.isRequired,\r\n\tresultPanel: PropTypes.node.isRequired\r\n}\r\n\r\n\r\nexport {SectionPanel}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {ResultPanel} from './result-panel'\r\nimport {SectionPanel} from './section-panel'\r\n\r\n// constants\r\nimport {ID_TYPE} from '../constants'\r\n\r\n\r\nconst containerStyle = {\r\n\tflexGrow: 1,\r\n\tflexShrink: 0,\r\n\tmarginBottom: 10,\r\n\twidth: '100%'\r\n}\r\n\r\n\r\nclass ResultsPanel extends React.Component\r\n{\r\n\trender()\r\n\t{\r\n\t\tconst {data:{mappings}, chosenSection} = this.props;\r\n\t\tconst renderableIds = mappings[chosenSection];\r\n\t\tconst renderables = this.areSections(renderableIds) ?\r\n\t\t\tthis.getSectionPanels(renderableIds) : this.getResultPanel(renderableIds);\r\n\r\n\t\treturn {renderables}
;\r\n\t}\r\n\r\n\tgetSectionPanels(ids)\r\n\t{\r\n\t\tconst {data:{mappings, sections}, hideSectionNamesInPlayer} = this.props;\r\n\r\n\t\tif (hideSectionNamesInPlayer){\r\n\t\t\treturn this.getResultPanelForAllSections(ids, mappings);\r\n\t\t}\r\n\r\n\t\treturn ids.map((id, index) => {\r\n\t\t\tconst headingNode = sections[id];\r\n\t\t\tconst resultPanel = this.getResultPanel(mappings[id]);\r\n\r\n\t\t\treturn ;\r\n\t\t});\r\n\t}\r\n\r\n\tgetResultPanel(ids)\r\n\t{\r\n\t\tconst {results} = this.props.data;\r\n\t\treturn {ids.map(id => results[id])} ;\r\n\t}\r\n\r\n\tgetResultPanelForAllSections(ids, mappings){\r\n\t\tconst results = ids.reduce((total, id) => {\r\n\t\t\treturn total.concat(mappings[id]);\r\n\t\t}, []);\r\n\r\n\t\t// let allResults = [];\r\n\t\t\r\n\t\t// ids.forEach(id=>{\r\n\t\t// \tconst ids = mappings[id];\r\n\t\t// \tallResults = allResults.concat(ids);\r\n\t\t// });\r\n\r\n\t\treturn this.getResultPanel(results);\t\t\r\n\t}\r\n\r\n\tareSections(renderableIds)\r\n\t{\r\n\t\treturn this.props.data.sections.hasOwnProperty(renderableIds[0]);\r\n\t}\r\n}\r\n\r\nResultsPanel.propTypes = {\r\n\tdata: PropTypes.shape({\r\n\t\tsections: PropTypes.objectOf(PropTypes.node).isRequired,\r\n\t\tresults: PropTypes.objectOf(PropTypes.node).isRequired,\r\n\t\tmappings: PropTypes.objectOf(PropTypes.arrayOf(ID_TYPE)).isRequired\r\n\t}).isRequired,\r\n\tchosenSection: ID_TYPE.isRequired\r\n}\r\n\r\n\r\nexport {ResultsPanel}","\r\n// npm\r\nimport PropTypes from 'prop-types'\r\n\r\nconst ID_TYPE = PropTypes.oneOfType([PropTypes.number, PropTypes.string]);\r\n\r\nexport {ID_TYPE}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\n\r\n// material-ui\r\nimport IconButton from \"@material-ui/core/IconButton\";\r\nimport MobileStepper from \"@material-ui/core/MobileStepper\";\r\nimport NavigateNextIcon from \"@material-ui/icons/NavigateNext\";\r\nimport NavigateBeforeIcon from \"@material-ui/icons/NavigateBefore\";\r\n\r\n// constants\r\nimport { ID_TYPE } from \"../constants\";\r\n\r\nconst calcSelectedNode = (selected, nodes) => {\r\n const currentNode = selected\r\n ? nodes.findIndex((node) => node.id === selected)\r\n : 0;\r\n\r\n return currentNode === -1 ? 0 : currentNode;\r\n};\r\n\r\nclass SectionNavigationItem extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n const { selected, nodes } = props;\r\n const currentNode = calcSelectedNode(selected, nodes);\r\n\r\n this.state = { currentNode };\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n const { selected, nodes } = this.props;\r\n if (prevProps.selected !== selected) {\r\n const currentNode = calcSelectedNode(selected, nodes);\r\n this.setState({ currentNode });\r\n }\r\n }\r\n\r\n render() {\r\n const props = {\r\n position: \"static\",\r\n steps: this.props.nodes.length,\r\n activeStep: this.state.currentNode,\r\n nextButton: this.getNavigationButton(1),\r\n backButton: this.getNavigationButton(-1),\r\n };\r\n\r\n return (\r\n \r\n {this.props.nodes[this.state.currentNode].node}\r\n \r\n
\r\n );\r\n }\r\n\r\n getNavigationButton(direction) {\r\n const { nodes } = this.props;\r\n const nextNode = this.state.currentNode + direction;\r\n\r\n const onClick = () => {\r\n this.setState({ currentNode: nextNode });\r\n this.props.onChange(nodes[nextNode].id);\r\n };\r\n\r\n const disabled = nextNode < 0 || nextNode === nodes.length;\r\n\r\n const Icon = direction < 0 ? NavigateBeforeIcon : NavigateNextIcon;\r\n return (\r\n \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nSectionNavigationItem.propTypes = {\r\n nodes: PropTypes.arrayOf(\r\n PropTypes.shape({\r\n id: ID_TYPE.isRequired,\r\n node: PropTypes.node.isRequired,\r\n })\r\n ).isRequired,\r\n onChange: PropTypes.func.isRequired,\r\n};\r\n\r\nexport { SectionNavigationItem };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Divider from '@material-ui/core/Divider'\r\n\r\n// react\r\nimport {SectionNavigationItem} from './navigation-item'\r\n\r\n// constants\r\nimport {ID_TYPE} from '../constants'\r\n\r\n\r\nconst containerStyle = {\r\n\twidth: '100%',\r\n\tflexShrink: 0\r\n}\r\n\r\n\r\nclass SectionNavigationPanel extends React.Component\r\n{\r\n\trender()\r\n\t{\r\n\t\tconst parentId = this.getParentId(this.props.chosenSection);\t\t\t\t\t\t\t\t\t// parent contains the mappings needed for the most deeply-nested nav item\r\n\t\treturn parentId ? this.getNavigationItems(parentId) : null;\r\n\t}\r\n\r\n\tgetNavigationItems(parentId)\r\n\t{\r\n\t\tconst navigationItems = [];\r\n\t\tconst {data:{sections, mappings}, onChange, chosenSection} = this.props;\r\n\r\n\t\twhile (parentId)\r\n\t\t{\r\n\t\t\tconst parentMapping = mappings[parentId];\r\n\t\t\tconst nodes = parentMapping.map(id => ({id, node: sections[id]}));\r\n\t\t\tnavigationItems.unshift();\r\n\t\t\tparentId = this.getParentId(parentId);\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{navigationItems}\r\n\t\t\t\t
\r\n\t\t\t
\r\n\t\t)\r\n\t}\r\n\r\n\tgetParentId(childId)\r\n\t{\r\n\t\tconst {mappings} = this.props.data;\r\n\t\tfor (let parentId in mappings) {\r\n\t\t\tconst mapping = mappings[parentId];\r\n\t\t\tif (mapping.includes(childId)) { return parentId; }\r\n\t\t}\r\n\t}\r\n}\r\n\r\nSectionNavigationPanel.propTypes = {\r\n\tdata: PropTypes.shape({\r\n\t\tsections: PropTypes.objectOf(PropTypes.node).isRequired,\r\n\t\tmappings: PropTypes.objectOf(PropTypes.arrayOf(ID_TYPE)).isRequired\r\n\t}).isRequired,\r\n\tonChange: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nexport {SectionNavigationPanel}","// RESPONSIBILITY:\r\n// --------------------------------------------------------------------------\r\n// This navigation panel renders the following:\r\n// 1. A section navigation panel for traversing through any nested sections\r\n// <- SECTION 1 ->\r\n// <- SECTION A ->\r\n// <- SEGMENT i ->\r\n// 2. A results panel displaying the results for the chosen section\r\n// [A] [B] [C] [D]\r\n// --------------------------------------------------------------------------\r\n\r\n// NOTE:\r\n// --------------------------------------------------------------------------\r\n// n = section nesting depth\r\n//\r\n// When the 'nestResults' prop is set to true, the sections panel will only\r\n// allow n-1 sections to be navigated. The final nested section will instead be\r\n// shown in the results panel, as follows:\r\n//\r\n// SECTION A\r\n// [i] [ii] [iii] [iv]\r\n//\r\n// SECTION B\r\n// [a] [b] [c] [d] [e]\r\n// -------------------------------------------------------------------------\r\n\r\n// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getContentData } from \"redux/reducers/exam/selectors\";\r\nimport { getCurrentQuestionId } from \"redux/reducers/exam/content/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getHideSectionNamesInPlayer} from 'redux/reducers/settings/client/selectors'\r\n\r\n// react\r\nimport { ResultsPanel } from \"./results/panel\";\r\nimport { SectionNavigationPanel } from \"./sections/panel\";\r\nimport {\r\n activityLogger,\r\n ACTIVITIES,\r\n} from \"libs/activity_logger/activity-logger\";\r\n\r\n// constants\r\nimport { ID_TYPE } from \"./constants\";\r\n\r\nconst containerStyle = {\r\n width: \"100%\",\r\n height: \"100%\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n};\r\n\r\nconst getFirstQuestionFromMappings = (id, mappings) => {\r\n const mapping = mappings[id];\r\n\r\n if (!mapping) return id;\r\n\r\n for (let i = 0; i < mapping.length; i++) {\r\n const id = mapping[i];\r\n const questionId = getFirstQuestionFromMappings(id, mappings);\r\n if (questionId) return questionId;\r\n }\r\n\r\n return null;\r\n};\r\n\r\nconst mappingsHasCurrentQuestion = (id, mappings, currentQuestionId) => {\r\n const mapping = mappings[id];\r\n\r\n if (!mapping) return false;\r\n\r\n if (mapping.indexOf(currentQuestionId) !== -1) {\r\n return true;\r\n }\r\n\r\n for (let i = 0; i < mapping.length; i++) {\r\n const id = mapping[i];\r\n if (mappingsHasCurrentQuestion(id, mappings, currentQuestionId)) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n};\r\n\r\nclass NavigationPanel extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n this.validateMappingIdsAreSections();\r\n this.validateExistenceOfRootMapping();\r\n this.state = { chosenSection: this.findChosenSection(\"root\") };\r\n\r\n this.updateChosenSection = (newSectionId) => {\r\n if (!this.questionDisplayedOnNewSection(newSectionId)) {\r\n const questionId = this.getFirstQuestionInSection(newSectionId);\r\n\r\n if (questionId) {\r\n const { onQuestionChange } = props;\r\n onQuestionChange(questionId);\r\n return;\r\n }\r\n }\r\n\r\n this.setState({\r\n chosenSection: this.findChosenSection(newSectionId, true),\r\n });\r\n };\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (this.props.currentQuestionId !== prevProps.currentQuestionId) {\r\n const { chosenSection } = this.state;\r\n const currentSection = this.findChosenSection(\"root\");\r\n if (chosenSection !== currentSection) {\r\n //this.setState({chosenSection: currentSection});\r\n this.setState({ chosenSection: currentSection }, () => {\r\n activityLogger.log(ACTIVITIES.MOVE_SECTION, {\r\n id: currentSection,\r\n });\r\n });\r\n }\r\n }\r\n }\r\n\r\n validateMappingIdsAreSections() {\r\n const { data } = this.props;\r\n const resultIds = Object.keys(data.results);\r\n const mappingIds = Object.keys(data.mappings);\r\n if (resultIds.some((resultId) => mappingIds.hasOwnProperty(resultId))) {\r\n throw \"NavigationPanel: A resultId can't be used as a mapping\";\r\n }\r\n }\r\n\r\n validateExistenceOfRootMapping() {\r\n if (!this.props.data.mappings.root) {\r\n throw \"NavigationPanel must have a root mapping\";\r\n }\r\n }\r\n\r\n findChosenSection(sectionId, changeManually = false) {\r\n const { currentQuestionId, data } = this.props;\r\n const { mappings, sections } = data;\r\n const currentMappings = mappings[sectionId];\r\n\r\n if (!sections.hasOwnProperty(currentMappings[0])) {\r\n return sectionId;\r\n }\r\n\r\n if (sectionId === \"root\" && !changeManually) {\r\n for (let i = 0; i < currentMappings.length; i++) {\r\n const currentMapping = currentMappings[i];\r\n const newSection = this.findSection(currentMapping);\r\n if (\r\n mappingsHasCurrentQuestion(\r\n newSection,\r\n mappings,\r\n currentQuestionId\r\n )\r\n ) {\r\n return newSection;\r\n }\r\n }\r\n }\r\n\r\n return this.findSection(sectionId);\r\n\r\n // while (currentMappings && sections.hasOwnProperty(currentMappings[0]))\r\n // {\r\n // \t// eslint-disable-next-line\r\n // \tvar parentResultsId = currentResultsId || sectionId;\r\n // \tvar currentResultsId = currentMappings[0];\r\n // \tcurrentMappings = mappings[currentResultsId];\r\n // }\r\n\r\n // return this.props.nestResults ? parentResultsId : currentResultsId;\r\n }\r\n\r\n findSection(sectionId) {\r\n const { mappings, sections } = this.props.data;\r\n let currentMappings = mappings[sectionId];\r\n\r\n while (currentMappings && sections.hasOwnProperty(currentMappings[0])) {\r\n // eslint-disable-next-line\r\n var parentResultsId = currentResultsId || sectionId;\r\n var currentResultsId = currentMappings[0];\r\n currentMappings = mappings[currentResultsId];\r\n }\r\n\r\n return this.props.nestResults ? parentResultsId : currentResultsId;\r\n }\r\n\r\n questionDisplayedOnNewSection(sectionId) {\r\n const { currentQuestionId, data } = this.props;\r\n const { mappings } = data;\r\n\r\n return mappingsHasCurrentQuestion(\r\n sectionId,\r\n mappings,\r\n currentQuestionId\r\n );\r\n }\r\n\r\n getFirstQuestionInSection(sectionId) {\r\n const { mappings } = this.props.data;\r\n return getFirstQuestionFromMappings(mappings[sectionId][0], mappings);\r\n }\r\n\r\n render() {\r\n const {\r\n props: { data, hideSectionNamesInPlayer },\r\n state: { chosenSection },\r\n } = this;\r\n\r\n return (\r\n \r\n \r\n \r\n
\r\n );\r\n }\r\n}\r\n\r\nNavigationPanel.propTypes = {\r\n data: PropTypes.shape({\r\n sections: PropTypes.objectOf(PropTypes.node).isRequired,\r\n results: PropTypes.objectOf(PropTypes.node).isRequired,\r\n mappings: PropTypes.objectOf(PropTypes.arrayOf(ID_TYPE)).isRequired,\r\n }).isRequired,\r\n nestResults: PropTypes.bool,\r\n};\r\n\r\nconst mapStoreToProps = (store) => {\r\n const contentData = getContentData(getExamData(store));\r\n const currentQuestionId = getCurrentQuestionId(contentData);\r\n const settingsData = getSettingsData(store);\r\n\tconst clientSettingsData = getClientSettingsData(settingsData); \r\n return {\r\n hideSectionNamesInPlayer: getHideSectionNamesInPlayer(clientSettingsData),\r\n currentQuestionId,\r\n };\r\n};\r\n\r\nNavigationPanel = connect(mapStoreToProps)(NavigationPanel);\r\n\r\nexport { NavigationPanel };\r\n","const calculateRootSectionsMarks = (sectionData, mappingsData) => {\r\n const root = mappingsData.get(\"root\");\r\n const rootMarks = [];\r\n\r\n if (!root) {\r\n return sectionData;\r\n }\r\n\r\n root.forEach((sectionId) => {\r\n const section = sectionData.get(sectionId);\r\n\r\n if (section) {\r\n const marks = section.get(\"marks\");\r\n\r\n if (isNaN(marks)) {\r\n rootMarks[sectionId] = getMarksForSection(\r\n sectionId,\r\n sectionData,\r\n mappingsData\r\n );\r\n }\r\n }\r\n });\r\n\r\n return sectionData.map((item, index) => {\r\n return rootMarks[index] ? item.set(\"marks\", rootMarks[index]) : item;\r\n });\r\n};\r\n\r\nconst getMarksForSection = (sectionId, sectionData, mappingsData) => {\r\n const section = mappingsData.get(sectionId);\r\n const marks = section.reduce((total, sectionId) => {\r\n const marks = sectionData.get(sectionId).get(\"marks\");\r\n if (!isNaN(marks)) total += marks;\r\n return total;\r\n }, 0);\r\n\r\n return marks;\r\n};\r\n\r\nexport { calculateRootSectionsMarks };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport {connect} from 'react-redux'\r\n\r\n// react\r\nimport {SectionHeading} from './section-heading'\r\nimport {QuestionButton} from './question-button'\r\nimport {NavigationPanel} from 'components/layout/navigation_panel/panel'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getPaperPartId} from 'redux/reducers/exam/content/complex-selectors'\r\nimport {getId} from 'redux/reducers/exam/content/questions/question/selectors'\r\nimport {getSectionsData, getQuestionsData, getMappingsData, getCurrentQuestionId} from 'redux/reducers/exam/content/selectors'\r\n\r\nimport {calculateRootSectionsMarks} from './section-marks'\r\n\r\n\r\nconst shouldInclude = (currentPaperPartId, getPaperPartId) => (id) =>\r\n{\r\n\tif (currentPaperPartId === id) { return true; }\r\n\treturn getPaperPartId(id) === currentPaperPartId;\r\n}\r\n\r\nconst mapStoreToProps = (store, {onQuestionChange}) =>\r\n{\r\n\tconst contentData = getContentData(getExamData(store));\r\n\tconst currentQuestionId = getCurrentQuestionId(contentData);\r\n\tconst _getPaperPartId = getPaperPartId(contentData);\r\n\tconst currentPaperPartId = _getPaperPartId(currentQuestionId);\r\n\r\n\tlet sectionsData = getSectionsData(contentData);\r\n\tlet questionsData = getQuestionsData(contentData);\r\n\tlet mappingsData = getMappingsData(contentData);\r\n\r\n\tif (currentPaperPartId) { // filter for nodes that only exist in the current paper part\r\n\t\tconst cachedResults = {};\r\n\t\tconst _shouldInclude = shouldInclude(currentPaperPartId, _getPaperPartId);\r\n\r\n\t\t// 1. Filter unneeded sections\r\n\t\tsectionsData = sectionsData.filter((_, sectionId) => {\r\n\t\t\tcachedResults[sectionId] = _shouldInclude(sectionId);\r\n\t\t\treturn cachedResults[sectionId];\r\n\t\t});\r\n\r\n\t\t// 2. Filter unneeded questions\r\n\t\tquestionsData = questionsData.filter((_, questionId) => {\r\n\t\t\tcachedResults[questionId] = _shouldInclude(questionId);\r\n\t\t\treturn cachedResults[questionId];\r\n\t\t});\r\n\r\n\t\t// 3. Filter unneeded mappings\r\n\t\tmappingsData = mappingsData.filter((_, mappingId) => {\r\n\t\t\treturn /*mappingId === 'root' || */cachedResults[mappingId];\r\n\t\t});\r\n\r\n\t\t// 4. Convert paper part mapping to be 'root'\r\n\t\tconst paperPartMapping = mappingsData.get(currentPaperPartId);\r\n\t\tmappingsData = mappingsData.delete(currentPaperPartId);\r\n\t\tmappingsData = mappingsData.set('root', paperPartMapping);\r\n\t}\r\n\r\n\tsectionsData = calculateRootSectionsMarks(sectionsData, mappingsData);\r\n\r\n\tconst sections = (sectionsData.map(sectionData => {\r\n\t\treturn ;\r\n\t})).toObject();\r\n\r\n\tconst results = (questionsData.map(questionData => {\r\n\t\tconst questionId = getId(questionData);\r\n\t\tconst onClick = () => onQuestionChange(questionId);\r\n\t\tconst props = {questionData, onClick, key: questionId};\r\n\t\treturn ;\r\n\t})).toObject();\r\n\r\n\tconst mappings = mappingsData.toJS();\r\n\r\n\treturn {\r\n\t\tdata: {sections, results, mappings},\r\n\t\tnestResults: true\r\n\t}\r\n}\r\n\r\nconst NavigationPanelContent = connect(mapStoreToProps)(NavigationPanel);\r\n\r\n\r\nexport {NavigationPanelContent}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {NavigationPanelTitle} from './panel-title'\r\nimport {NavigationPanelContent} from './panel-content'\r\nimport { MaterialScrollWrapper } from \"components/layout/scroll_wrapper/material-scroll-wrapper\";\r\n\r\n// constants\r\nimport {contentBarHeight} from '../../../constants'\r\n\r\n\r\nconst wrapperStyle = {\r\n\twidth: 192,\r\n\theight: '100%',\r\n\tdisplay: 'flex',\r\n\talignItems: 'stretch',\r\n\tflexDirection: 'column',\r\n\tmarginLeft: 8,\r\n\tmarginRight: 8\r\n}\r\n\r\nconst QuestionsNavigationPanel = (onQuestionChange) => (\r\n\t\r\n\t\t
\r\n\t\t\t \r\n\t\t
\r\n\t\t
\r\n\t\t\r\n\t\t\t\r\n\t\t
\r\n\t\t \r\n\t
\r\n)\r\n\r\n\r\nexport {QuestionsNavigationPanel}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport ChromeReaderIcon from '@material-ui/icons/ChromeReaderMode'\r\n\r\n// react\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {ActionButton} from 'components/layout/action_button/button'\r\n\r\n// other\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst examInstructionsMessageId = MESSAGE_IDS.EXAM.INSTRUCTIONS;\r\n\r\n\r\nlet InstructionsButton = ({onClick, messages}) => (\r\n\t\r\n\t\t{messages[examInstructionsMessageId]}\r\n\t \r\n)\r\n\r\nInstructionsButton.propTypes = {\r\n\tonClick: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nInstructionsButton = withMessages(InstructionsButton);\r\n\r\n\r\nexport {InstructionsButton}","import React, { Component } from \"react\";\r\n\r\nimport DialogTitle from \"@material-ui/core/DialogTitle\";\r\nimport Dialog from \"@material-ui/core/Dialog\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport LinearProgress from \"@material-ui/core/LinearProgress\";\r\nimport Button from \"@material-ui/core/Button\";\r\nimport PrintIcon from \"@material-ui/icons/Print\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\n\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\nclass PrintStaticTextDialog extends Component {\r\n state = {};\r\n\r\n getTitle() {\r\n const { printing, error, classes } = this.props;\r\n if (printing) {\r\n return (\r\n \r\n
\r\n \r\n \r\n
Printing
\r\n
\r\n );\r\n } else if (error) {\r\n return (\r\n \r\n );\r\n }\r\n }\r\n\r\n getContent() {\r\n const { printing, error, classes } = this.props;\r\n if (printing) {\r\n return (\r\n \r\n \r\n
\r\n );\r\n } else if (error) {\r\n const { onErrorClose } = this.props;\r\n const buttonProps = {\r\n onClick: onErrorClose,\r\n variant: \"contained\",\r\n colour: \"secondary\",\r\n };\r\n\r\n return (\r\n \r\n
\r\n There was a problem printing\r\n \r\n
\r\n Close \r\n
\r\n
\r\n );\r\n }\r\n }\r\n\r\n render() {\r\n const { printing, error, classes } = this.props;\r\n const dialogProps = {\r\n \"aria-labelledby\": \"print-static-text-dialog-title\",\r\n classes: { paper: classes.paper },\r\n open: printing || error,\r\n className: classes.dialog,\r\n };\r\n return (\r\n \r\n \r\n {this.getTitle()}\r\n \r\n {this.getContent()}\r\n \r\n );\r\n }\r\n}\r\n\r\nconst styles = ({ spacing }) => ({\r\n paper: { minWidth: \"500px\" },\r\n progress: {\r\n margin: `${spacing.unit * 2}px ${spacing.unit * 2}px ${\r\n spacing.unit * 4\r\n }px ${spacing.unit * 2}px`,\r\n },\r\n title: {\r\n display: \"flex\",\r\n justifyContent: \"flex-start\",\r\n alignItems: \"center\",\r\n \"&>div:last-child\": {\r\n marginLeft: `${spacing.unit * 1}px`,\r\n },\r\n },\r\n errorIcon: {\r\n width: spacing.unit * 5,\r\n height: spacing.unit * 5,\r\n },\r\n error: {\r\n margin: `${spacing.unit * 2}px`,\r\n },\r\n errorButton: {\r\n display: \"flex\",\r\n justifyContent: \"flex-end\",\r\n },\r\n});\r\n\r\nPrintStaticTextDialog = withStyles(styles)(PrintStaticTextDialog);\r\n\r\nexport { PrintStaticTextDialog };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\nimport * as signalR from \"@microsoft/signalr\";\r\n\r\nimport {PrintStaticTextDialog} from './print-static-text-dialog'\r\n\r\nimport IconButton from \"@material-ui/core/IconButton\";\r\nimport PrintIcon from \"@material-ui/icons/Print\";\r\n\r\nimport {\r\n getExamData,\r\n getSessionData,\r\n getSchedulesData,\r\n getSettingsData,\r\n} from \"redux/reducers/selectors\";\r\nimport {\r\n getFormRunGuid,\r\n getScheduleGuid,\r\n getGuid as getExamGuid,\r\n} from \"redux/reducers/exam/selectors\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getUserGuid } from \"redux/reducers/session/user/selectors\";\r\nimport { getScheduleDataByExamGuid } from \"redux/reducers/schedules/selectors\";\r\nimport {\r\n getScheduleType,\r\n getExamTypeGuid,\r\n} from \"redux/reducers/schedules/schedule/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport {\r\n getApiEndpointUrl,\r\n getHubUrl,\r\n} from \"redux/reducers/settings/app/selectors\";\r\n\r\nimport { assessmentApi } from \"libs/api/interface/api-assessment\";\r\n\r\nconst getUrlParameterStringFromObject = (obj) => {\r\n let str = \"\";\r\n\r\n if (obj) {\r\n for (let key in obj) {\r\n if (str !== \"\") {\r\n str += \"&\";\r\n }\r\n str += `${key}=${encodeURIComponent(obj[key])}`;\r\n }\r\n }\r\n\r\n return str;\r\n};\r\n\r\nclass PrintStaticTextButton extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleOnClick = this.handleOnClick.bind(this);\r\n this.printByApiCall = this.printByApiCall.bind(this);\r\n this.printByHub = this.printByHub.bind(this);\r\n this.handleCloseDialog = this.handleCloseDialog.bind(this);\r\n\r\n this.state = { printing: false, error: false };\r\n }\r\n\r\n handleOnClick() {\r\n this.printByApiCall();\r\n //this.printByHub();\r\n }\r\n\r\n getPayload() {\r\n const {\r\n questionId,\r\n formRunGuid,\r\n userGuid,\r\n scheduleGuid,\r\n scheduleType,\r\n examGuid,\r\n examTypeGuid,\r\n } = this.props;\r\n\r\n const payload = {\r\n questionId,\r\n userGuid,\r\n scheduleGuid,\r\n scheduleType,\r\n examGuid,\r\n examTypeGuid,\r\n formRunGuid,\r\n };\r\n\r\n return payload;\r\n }\r\n\r\n handleCloseDialog = () => {\r\n this.setState({ printing: false, error: false });\r\n } \r\n\r\n printByApiCall() {\r\n const { apiEndpointUrl } = this.props;\r\n\r\n const payload = {...this.getPayload(), filename:'question.pdf'};\r\n\r\n const handleError = (e) => {\r\n this.setState({ printing: false, error: true });\r\n };\r\n\r\n this.setState({ printing: true });\r\n\r\n assessmentApi\r\n .getPrintStaticTextUrl(payload)\r\n .then((response) => {\r\n const data = JSON.parse(response);\r\n if (data) {\r\n const { link, error } = data;\r\n\r\n if (link) {\r\n this.handleCloseDialog();\r\n\r\n const url = `${apiEndpointUrl}${link}&inline=true`;\r\n window.open(url);\r\n } else if (error) {\r\n handleError(error);\r\n }\r\n }\r\n }, handleError)\r\n .catch(() => {\r\n handleError();\r\n });\r\n }\r\n\r\n printByHub() {\r\n const { hubUrl } = this.props;\r\n const method = \"apiProgressHub\";\r\n const url = `${hubUrl}${method}`;\r\n\r\n let connection = new signalR.HubConnectionBuilder()\r\n .withUrl(url)\r\n .build();\r\n\r\n connection.on(\"update\", (data) => {\r\n debugger;\r\n });\r\n\r\n connection\r\n .start()\r\n .then((data) => {\r\n debugger;\r\n })\r\n .catch((e) => {\r\n console.log(e);\r\n debugger;\r\n });\r\n }\r\n\r\n render() {\r\n const { printing, error } = this.state;\r\n\r\n const iconProps = { onClick: this.handleOnClick };\r\n\r\n const dialogProps = { printing, error, onErrorClose: this.handleCloseDialog };\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store, ownProps) => {\r\n const questionId = parseInt(ownProps.questionId, 10);\r\n\r\n const examData = getExamData(store);\r\n const formRunGuid = getFormRunGuid(examData);\r\n const scheduleGuid = getScheduleGuid(examData);\r\n const examGuid = getExamGuid(examData);\r\n\r\n const sessionData = getSessionData(store);\r\n const userData = getUserSessionData(sessionData);\r\n const userGuid = getUserGuid(userData);\r\n\r\n const schedulesData = getSchedulesData(store);\r\n const scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n\r\n const scheduleType = getScheduleType(scheduleData);\r\n const examTypeGuid = getExamTypeGuid(scheduleData);\r\n\r\n const settingsData = getSettingsData(store);\r\n const appSettingsData = getAppSettingsData(settingsData);\r\n const apiEndpointUrl = getApiEndpointUrl(appSettingsData);\r\n const hubUrl = getHubUrl(appSettingsData);\r\n\r\n return {\r\n questionId,\r\n formRunGuid,\r\n userGuid,\r\n scheduleGuid,\r\n scheduleType,\r\n examGuid,\r\n examTypeGuid,\r\n apiEndpointUrl,\r\n hubUrl,\r\n };\r\n};\r\n\r\nPrintStaticTextButton = connect(mapStoreToProps)(PrintStaticTextButton);\r\n\r\nexport { PrintStaticTextButton };","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {milliseconds, seconds} from 'time-convert'\r\nimport ImmutablePropTypes from 'immutable-prop-types'\r\n\r\n// redux (selectors)\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getTimeRemaining} from 'redux/reducers/exam/complex-selectors'\r\nimport {getCurrentQuestionId} from 'redux/reducers/exam/content/selectors'\r\nimport {getPaperPartId} from 'redux/reducers/exam/content/complex-selectors'\r\n\r\n\r\nclass ExamTimeSaver extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.timeOnLastSave = props.timeRemaining;\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n\r\n\tcomponentDidUpdate(previousProps)\r\n\t{\r\n\t\tif (this.shouldSave(previousProps)) {\r\n\t\t\tthis.saveTime();\r\n\t\t}\r\n\t}\r\n\r\n\tshouldSave(previousProps)\r\n\t{\r\n\t\tif (previousProps.paperPartId !== this.props.paperPartId) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\tconst elapsedTime = this.timeOnLastSave - this.props.timeRemaining;\r\n\t\treturn elapsedTime >= this.props.interval;\r\n\t}\r\n\r\n\tsaveTime()\r\n\t{\r\n\t\tthis.props.saveTime();\r\n\t\tthis.timeOnLastSave = this.props.timeRemaining;\r\n\t}\r\n}\r\n\r\nExamTimeSaver.defaultProps = {\r\n\tinterval: 30\r\n}\r\n\r\nExamTimeSaver.propTypes = {\r\n\tpaperPartId: PropTypes.number,\r\n\tsaveTime: PropTypes.func.isRequired,\r\n\tinterval: PropTypes.number.isRequired,\r\n\ttimeRemaining: PropTypes.number.isRequired\r\n}\r\n\r\n\r\nexport {ExamTimeSaver}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport ImmutablePropTypes from \"immutable-prop-types\";\r\n\r\n// utils\r\nimport { getProp } from \"custom/object-helper\";\r\n\r\n// constants\r\nimport { MULTI_TEXT, RADIOS_TEXT } from \"constants/question-types\";\r\n\r\nclass MultiTextSaver extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n this.resetTimer();\r\n }\r\n\r\n render() {\r\n return null;\r\n }\r\n\r\n componentDidUpdate(previousProps) {\r\n if (this.shouldResetElapsedTime(previousProps)) {\r\n this.resetTimer();\r\n } else if (this.shouldSaveQuestion()) {\r\n this.saveQuestion();\r\n }\r\n }\r\n\r\n shouldResetElapsedTime(previousProps) {\r\n return previousProps.question.id !== this.props.question.id;\r\n }\r\n\r\n resetTimer() {\r\n this.timeOnLastSave = this.props.timeRemaining;\r\n this.answerOnLastSave = this.props.question.answer;\r\n }\r\n\r\n shouldSaveQuestion() {\r\n const { question } = this.props;\r\n const questionTypes = [MULTI_TEXT, RADIOS_TEXT];\r\n\r\n if (\r\n questionTypes.indexOf(question.type) === -1 ||\r\n !this.hasAnswerChanged()\r\n ) {\r\n // if (question.type !== MULTI_TEXT || !this.hasAnswerChanged()) {\r\n return false;\r\n }\r\n\r\n const elapsedTime = this.timeOnLastSave - this.props.timeRemaining;\r\n return elapsedTime >= this.props.interval;\r\n }\r\n\r\n\thasAnswerChanged() {\r\n const { question } = this.props;\r\n const answerName =\r\n question.type === RADIOS_TEXT ? \"otherText\" : \"answerText\";\r\n\r\n const previousAnswer = getProp(this, `answerOnLastSave.${answerName}`);\r\n const newAnswer = getProp(this.props, `question.answer.${answerName}`);\r\n\r\n return previousAnswer !== newAnswer;\r\n }\r\n\r\n saveQuestion() {\r\n this.props.saveQuestion();\r\n this.resetTimer();\r\n }\r\n}\r\n\r\nMultiTextSaver.defaultProps = {\r\n interval: 30,\r\n};\r\n\r\nMultiTextSaver.propTypes = {\r\n saveQuestion: PropTypes.func.isRequired,\r\n interval: PropTypes.number.isRequired,\r\n timeRemaining: PropTypes.number.isRequired,\r\n question: PropTypes.shape({\r\n id: PropTypes.number.isRequired,\r\n type: PropTypes.string.isRequired,\r\n answer: PropTypes.object.isRequired,\r\n }).isRequired,\r\n};\r\n\r\nexport { MultiTextSaver };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport ImmutablePropTypes from 'immutable-prop-types'\r\n\r\n\r\nclass ExamQuestionSaver extends React.Component\r\n{\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n\r\n\tcomponentDidUpdate(previousProps)\r\n\t{\r\n\t\tif (previousProps.currentQuestionId !== this.props.currentQuestionId) {\r\n\t\t\t// console.log('Save previous question',previousProps.currentQuestionId, this.props.currentQuestionId);\r\n\t\t\tthis.props.savePreviousQuestion();\r\n\t\t}\r\n\r\n\t\tif (previousProps.paperPartId !== this.props.paperPartId) {\r\n\t\t\t// console.log('Changed section - save current question',previousProps.paperPartId, this.props.paperPartId);\r\n\t\t\tthis.props.saveCurrentQuestion();\r\n\t\t}\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tthis.props.saveCurrentQuestion();\r\n\t}\r\n}\r\n\r\nExamQuestionSaver.propTypes = {\r\n\tsavePreviousQuestion: PropTypes.func.isRequired,\r\n\tsaveCurrentQuestion: PropTypes.func.isRequired,\r\n\tcurrentQuestionId: PropTypes.number.isRequired\r\n}\r\n\r\n\r\nexport {ExamQuestionSaver}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { milliseconds, seconds } from \"time-convert\";\r\n\r\n// react (hocs)\r\nimport { withExamRequestSaver } from \"components/hocs/exam_request_saver\";\r\n\r\n// react\r\nimport { ExamTimeSaver } from \"./time\";\r\nimport { MultiTextSaver } from \"./multi-text\";\r\nimport { ExamQuestionSaver } from \"./question\";\r\n\r\nimport { getTimeStamp } from \"utils/time-stamp\";\r\n\r\n// redux (selectors)\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getPaperPartId } from \"redux/reducers/exam/content/complex-selectors\";\r\nimport { getFormRunGuid, getContentData } from \"redux/reducers/exam/selectors\";\r\nimport { getQuestionDataById } from \"redux/reducers/exam/content/questions/selectors\";\r\nimport {\r\n getCurrentQuestionId,\r\n getQuestionsData,\r\n} from \"redux/reducers/exam/content/selectors\";\r\nimport { getWorkings } from \"redux/reducers/exam/content/questions/question/workings/selectors\";\r\nimport {\r\n getTimeRemaining,\r\n getReadingTimeRemaining,\r\n} from \"redux/reducers/exam/complex-selectors\";\r\nimport {\r\n getType,\r\n getServerAnswer,\r\n getNumber,\r\n getWorkingsData,\r\n getVersionId,\r\n getVersionNumber,\r\n} from \"redux/reducers/exam/content/questions/question/selectors\";\r\nimport { getTimeData } from \"redux/reducers/exam/selectors\";\r\nimport { getExamElapsedTime } from \"redux/reducers/exam/time/selectors\";\r\n\r\n// constants\r\nimport { MULTI_TEXT, RADIOS_TEXT } from \"constants/question-types\";\r\n\r\n// ExamSaver (not connected to store)\r\n// -------------------------------------------------\r\n\r\nclass ExamSaver extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n this.initializeBoundMethods();\r\n }\r\n\r\n initializeBoundMethods() {\r\n const saveQuestion = (props) => {\r\n const { question } = props;\r\n const answer = this.props.getQuestionAnswer(question.id);\r\n if (!answer) {\r\n return;\r\n }\r\n\r\n const payload = {\r\n timeRemaining: props.timeRemaining,\r\n elapsedTime: this.props.elapsedTime,\r\n page: question.number,\r\n formRunGuid: props.formRunGuid,\r\n questionNumber: question.number || this.props.question.number,\r\n questionVersionId:\r\n question.versionId || this.props.question.versionId,\r\n questionVersionNumber:\r\n question.versionNumber || this.props.question.versionNumber,\r\n workings: question.workings,\r\n objectID: question.id,\r\n ...answer,\r\n };\r\n\r\n props.examRequestSaver.saveAnswer(payload);\r\n };\r\n\r\n this.saveCurrentQuestion = () => {\r\n saveQuestion(this.props);\r\n };\r\n\r\n this.savePreviousQuestion = () => {\r\n if (!this.previousProps) {\r\n alert(\"Can't save previous question\");\r\n }\r\n saveQuestion(this.previousProps);\r\n };\r\n\r\n this.saveTime = () => {\r\n const payload = {\r\n formRunGuid: this.props.formRunGuid,\r\n timeRemaining: this.props.timeRemaining,\r\n elapsedTime: this.props.elapsedTime,\r\n page: this.props.question.number,\r\n ClientTime: getTimeStamp(),\r\n };\r\n\r\n this.props.examRequestSaver.saveExamTime(payload);\r\n };\r\n }\r\n\r\n render() {\r\n return (\r\n \r\n {this.ExamTimeSaver}\r\n {this.QuestionSaver}\r\n {this.MultiTextSaver}\r\n \r\n );\r\n }\r\n\r\n componentDidUpdate(previousProps) {\r\n this.previousProps = previousProps;\r\n }\r\n\r\n get ExamTimeSaver() {\r\n const { question } = this.props;\r\n\r\n const props = {\r\n saveTime: this.saveTime,\r\n paperPartId: question.paperPartId,\r\n timeRemaining: this.props.timeRemaining,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n get QuestionSaver() {\r\n const { readingTime } = this.props;\r\n if (readingTime) {\r\n return null;\r\n }\r\n\r\n const props = {\r\n savePreviousQuestion: this.savePreviousQuestion,\r\n saveCurrentQuestion: this.saveCurrentQuestion,\r\n currentQuestionId: this.props.question.id,\r\n paperPartId: this.props.question.paperPartId,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n get MultiTextSaver() {\r\n const { readingTime } = this.props;\r\n if (readingTime) {\r\n return null;\r\n }\r\n\r\n const { question } = this.props;\r\n if (question.type !== MULTI_TEXT && question.type !== RADIOS_TEXT) {\r\n return null;\r\n }\r\n\r\n const props = {\r\n timeRemaining: this.props.timeRemaining,\r\n saveQuestion: this.saveCurrentQuestion,\r\n question: {\r\n id: question.id,\r\n type: question.type,\r\n answer: this.props.getQuestionAnswer(question.id),\r\n },\r\n };\r\n\r\n return ;\r\n }\r\n}\r\n\r\nExamSaver.propTypes = {\r\n examRequestSaver: PropTypes.shape({\r\n saveAnswer: PropTypes.func.isRequired,\r\n saveExamTime: PropTypes.func.isRequired,\r\n }).isRequired,\r\n question: PropTypes.shape({\r\n type: PropTypes.string.isRequired,\r\n answer: PropTypes.any,\r\n id: PropTypes.number.isRequired,\r\n number: PropTypes.number.isRequired,\r\n versionId: PropTypes.number.isRequired,\r\n versionNumber: PropTypes.number.isRequired,\r\n paperPartId: PropTypes.number,\r\n workings: PropTypes.string,\r\n }),\r\n formRunGuid: PropTypes.string.isRequired,\r\n timeRemaining: PropTypes.number.isRequired,\r\n readingTime: PropTypes.bool.isRequired,\r\n};\r\n\r\n// ExamSaver (connected to store)\r\n// -------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const contentData = getContentData(examData);\r\n const questionsData = getQuestionsData(contentData);\r\n\r\n const questionId = getCurrentQuestionId(contentData);\r\n const questionData = getQuestionDataById(questionsData)(questionId);\r\n const workingsData = getWorkingsData(questionData);\r\n\r\n const timeRemaining = getTimeRemaining(examData, questionId);\r\n const elapsedTime = getExamElapsedTime(getTimeData(examData));\r\n\r\n return {\r\n formRunGuid: getFormRunGuid(examData),\r\n readingTime: getReadingTimeRemaining(examData, questionId) > 0,\r\n timeRemaining: Math.round(seconds.from(milliseconds)(timeRemaining)),\r\n elapsedTime,\r\n question: {\r\n id: questionId,\r\n type: getType(questionData),\r\n number: getNumber(questionData),\r\n paperPartId: getPaperPartId(contentData)(questionId),\r\n workings: workingsData ? getWorkings(workingsData) : undefined,\r\n versionId: getVersionId(questionData),\r\n versionNumber: getVersionNumber(questionData),\r\n },\r\n getQuestionAnswer: (id) => {\r\n const questionData = getQuestionDataById(questionsData)(id);\r\n\r\n return getServerAnswer(questionData);\r\n },\r\n };\r\n};\r\n\r\nExamSaver = connect(mapStoreToProps)(ExamSaver);\r\nExamSaver = withExamRequestSaver(ExamSaver);\r\n\r\nexport { ExamSaver };\r\n","\r\nimport {SET_CURRENT_QUESTION_ID} from 'redux/reducers/exam/content/action-types'\r\nimport {SET_COMPLETE} from 'redux/reducers/exam/content/sections/section/action-types'\r\n\r\n\r\nconst setCurrentQuestionId = (value) => ({\r\n\ttype: SET_CURRENT_QUESTION_ID,\r\n\tvalue\r\n});\r\n\r\nconst setPaperPartComplete = (id) => ({\r\n\ttype: SET_COMPLETE,\r\n\tid\r\n})\r\n\r\n\r\nexport {setCurrentQuestionId, setPaperPartComplete}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { milliseconds, seconds } from \"time-convert\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\n// material-ui\r\nimport Button from \"@material-ui/core/Button\";\r\nimport MenuIcon from \"@material-ui/icons/Menu\";\r\nimport BuildIcon from \"@material-ui/icons/Build\";\r\nimport IconButton from \"@material-ui/core/IconButton\";\r\nimport DownloadIcon from \"@material-ui/icons/CloudDownload\";\r\nimport UploadIcon from \"@material-ui/icons/CloudUploadOutlined\";\r\nimport ImportContactsIcon from \"@material-ui/icons/ImportContacts\";\r\n\r\n// react\r\nimport { ExamPopup, POPUP_TYPES } from \"./exam_popup\";\r\nimport { ExamLayout } from \"components/pages/exam/_layout/layout\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport { ExamTimer } from \"components/pages/exam/running/exam-timer\";\r\nimport { ExamContent } from \"components/pages/exam/_layout/content/panel/content\";\r\nimport { UploadPanel } from \"components/pages/exam/_layout/side/panels/upload/panel\";\r\nimport { WorkingsPanel } from \"components/pages/exam/_layout/side/panels/workings/panel\";\r\nimport { MiniQuestionNavigation } from \"components/pages/exam/running/mini-question-navigation\";\r\nimport { FinishExamButton } from \"components/pages/exam/running/questions_screen/finish-button\";\r\nimport { StartAnsweringButton } from \"components/pages/exam/running/reading_screen/start-button\";\r\nimport { CachedPdfResourcePanel } from \"components/pages/exam/_layout/side/panels/pdf_resource/index\";\r\nimport { QuestionsNavigationPanel } from \"components/pages/exam/_layout/side/panels/navigation/panel\";\r\nimport { InstructionsButton } from \"components/pages/exam/running/questions_screen/instructions-button\";\r\nimport { PrintStaticTextButton } from \"components/pages/exam/running/questions_screen/print_static_text/print-static-text-button\";\r\nimport {\r\n activityLogger,\r\n ACTIVITIES,\r\n} from \"libs/activity_logger/activity-logger\";\r\nimport { DisplayAllQuestionsBar } from \"components/pages/exam/_layout/content/panel/display_all_questions/display-all-questions-bar\";\r\n\r\n// react (temporary!!!!)\r\nimport { ExamSaver } from \"components/pages/exam/exam_saver\";\r\n\r\n// redux (selectors)\r\nimport { getExamData, getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getReadingTimeRemaining } from \"redux/reducers/exam/complex-selectors\";\r\nimport { canBypassReadingTime } from \"redux/reducers/exam/time/selectors\";\r\nimport { getType } from \"redux/reducers/exam/content/questions/question/selectors\";\r\nimport { getSectionDataById } from \"redux/reducers/exam/content/sections/selectors\";\r\nimport { getUploadFileTypesAllowed } from \"redux/reducers/settings/client/selectors\";\r\nimport { getQuestionDataById } from \"redux/reducers/exam/content/questions/selectors\";\r\nimport { getSectionName } from \"redux/reducers/exam/content/sections/section/selectors\";\r\nimport {\r\n getContentData,\r\n getFormRunGuid,\r\n isPracticeExam,\r\n getTimeData,\r\n} from \"redux/reducers/exam/selectors\";\r\nimport {\r\n getQuestionsData,\r\n getSectionsData,\r\n getCurrentQuestionId,\r\n} from \"redux/reducers/exam/content/selectors\";\r\nimport {\r\n getResourceUrl,\r\n hasUnansweredQuestions,\r\n hasPaperPartUnansweredQuestions,\r\n getPaperPartId,\r\n getNextPaperPartId,\r\n getFirstQuestionIdInSection,\r\n requiresWorkings,\r\n requiresCalculator,\r\n} from \"redux/reducers/exam/content/complex-selectors\";\r\nimport { getSessionData } from \"redux/reducers/selectors\";\r\nimport { getClientSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getOrgId } from \"redux/reducers/session/client/selectors\";\r\nimport {\r\n getShowExamInFullScreen,\r\n getInitialFullScreen,\r\n} from \"redux/reducers/exam/selectors\";\r\n\r\n// constants\r\nimport { ICT, STATIC_TEXT } from \"constants/question-types\";\r\n\r\n// redux (actions)\r\nimport { setPaperPartComplete } from \"./actions\";\r\n\r\n// redux (action-types)\r\nimport { SET_READING_TIME_REMAINING } from \"redux/reducers/exam/time/action-types\";\r\nimport { SET_READING_TIME_REMAINING as SET_SECTION_READING_TIME_REMAINING } from \"redux/reducers/exam/content/sections/section/action-types\";\r\n\r\nimport { closeFullScreen } from \"components/pages/_exam/initialized/running/leave_resume_exam/full_screen/helper\"\r\n\r\nconst setReadingTimeRemaining = (value) => ({\r\n type: SET_READING_TIME_REMAINING,\r\n value: seconds.from(milliseconds)(value),\r\n});\r\n\r\nconst setSectionReadingTimeRemaining = (value, id) => ({\r\n type: SET_SECTION_READING_TIME_REMAINING,\r\n value: seconds.from(milliseconds)(value),\r\n id,\r\n});\r\n\r\nconst doesEndWith = (fileName, extension = \"pdf\") => {\r\n if (check.nonEmptyString(fileName) && check.nonEmptyString(extension)) {\r\n const parts = fileName.split(\".\");\r\n\r\n if (check.nonEmptyArray(parts)) {\r\n const endPart = parts[parts.length - 1];\r\n if (check.nonEmptyString(endPart)) {\r\n return endPart.startsWith(extension);\r\n }\r\n }\r\n }\r\n\r\n return false;\r\n};\r\n\r\n// ExamQuestionPage (not connected to store)\r\n// -----------------------------------------------\r\n\r\nclass ExamQuestionPage extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.state = {\r\n popup: null,\r\n popupData: {\r\n paperPartName: props.paperPartName,\r\n nextPaperPartName: props.nextPaperPartName,\r\n },\r\n };\r\n\r\n this.popupType = null;\r\n\r\n this.closePopup = () => {\r\n this.setState({ popup: null });\r\n };\r\n\r\n this.openPopup = (popupType) => () => {\r\n this.setState({ popup: popupType });\r\n };\r\n\r\n this.onFinish = () => {\r\n const { needToCloseFullScreen } = this.props;\r\n\r\n this.setState({ popup: null }, () => {\r\n activityLogger\r\n .log(ACTIVITIES.CONFIRM_POPUP, { popup: this.popupType })\r\n .then(() => {\r\n if (needToCloseFullScreen) {\r\n const { nextPaperPartName } = this.props;\r\n\r\n if (!check.nonEmptyString(nextPaperPartName)) {\r\n closeFullScreen();\r\n }\r\n }\r\n \r\n this.props.onFinish();\r\n });\r\n });\r\n };\r\n\r\n this.timesUp = () => {\r\n const { needToCloseFullScreen } = this.props;\r\n activityLogger.log(ACTIVITIES.TIMED_OUT, {}).then(() => {\r\n if (needToCloseFullScreen) {\r\n closeFullScreen();\r\n }\r\n\r\n this.props.onFinish();\r\n });\r\n };\r\n\r\n this.paperPartTimedOut = (timerPaperPartId) => {\r\n if (timerPaperPartId) {\r\n const { currentPaperPartId } = this.props;\r\n\r\n if (currentPaperPartId !== timerPaperPartId) {\r\n activityLogger.log(ACTIVITIES.POPUP_ERROR, {\r\n currentPaperPartId,\r\n timerPaperPartId,\r\n });\r\n\r\n return;\r\n }\r\n }\r\n\r\n this.setState({ popup: POPUP_TYPES.PART_FINISHED });\r\n };\r\n }\r\n\r\n tryUpdatePopupData() {\r\n if (this.props.paperPartName !== this.state.popupData.paperPartName) {\r\n const { paperPartName, nextPaperPartName } = this.props;\r\n this.setState({ popupData: { paperPartName, nextPaperPartName } });\r\n }\r\n }\r\n\r\n checkPopupChange(prevState) {\r\n const { popup: prevPopup } = prevState;\r\n const { popup: newPopup } = this.state;\r\n\r\n if (prevPopup !== newPopup) {\r\n const payload = {\r\n prevPopup,\r\n newPopup,\r\n };\r\n\r\n activityLogger.log(ACTIVITIES.DISPLAY_POPUP, payload);\r\n }\r\n }\r\n\r\n render() {\r\n const examLayoutProps = {\r\n bar: {\r\n content: (\r\n \r\n \r\n
\r\n \r\n {this.StartOrFinishButton}\r\n {this.ExamPopup}\r\n {this.ExamTimer}\r\n {this.DownloadResourceButton}\r\n {this.PrintResourceButton}\r\n \r\n ),\r\n },\r\n centre: {\r\n bar: (\r\n \r\n \r\n
\r\n ),\r\n content: (\r\n \r\n \r\n
\r\n ),\r\n },\r\n left: {\r\n Icon: MenuIcon,\r\n content: QuestionsNavigationPanel(this.props.onQuestionChange), // See error for why I do this\r\n },\r\n right: this.RightContent,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n componentDidUpdate(prevProps, prevState) {\r\n this.tryUpdatePopupData();\r\n this.checkPopupChange(prevState);\r\n }\r\n\r\n get ExamPopup() {\r\n const { popup, popupData } = this.state;\r\n\r\n if (!check.assigned(popup)) {\r\n return null;\r\n }\r\n\r\n const popupProps = {\r\n type: popup,\r\n data: popupData,\r\n onClose: this.closePopup,\r\n onConfirm: this.getPopupOnConfirm(popup),\r\n };\r\n\r\n return ;\r\n }\r\n\r\n getPopupOnConfirm(type) {\r\n this.popupType = type;\r\n\r\n switch (type) {\r\n case POPUP_TYPES.EXAM_FINISH_CONFIRM:\r\n return this.props.hasUnansweredQuestions\r\n ? this.openPopup(POPUP_TYPES.EXAM_FINISH_CONFIRM_2)\r\n : this.onFinish;\r\n case POPUP_TYPES.PART_FINISH_CONFIRM:\r\n return this.props.hasUnansweredQuestions\r\n ? this.openPopup(POPUP_TYPES.PART_FINISH_CONFIRM_2)\r\n : this.onFinish;\r\n case POPUP_TYPES.PRACTICE_EXAM_FINISH_CONFIRM:\r\n return this.openPopup(\r\n POPUP_TYPES.PRACTICE_EXAM_FINISH_CONFIRM_2\r\n );\r\n case POPUP_TYPES.PRACTICE_EXAM_FINISH_CONFIRM_2:\r\n case POPUP_TYPES.EXAM_FINISH_CONFIRM_2:\r\n case POPUP_TYPES.PART_FINISH_CONFIRM_2:\r\n case POPUP_TYPES.PART_FINISHED:\r\n return this.onFinish;\r\n default:\r\n return null;\r\n }\r\n }\r\n\r\n get ExamTimer() {\r\n const props = {\r\n timerPaperPartId: this.props.currentPaperPartId,\r\n reading: this.props.inReadingTime,\r\n currentQuestionId: this.props.questionId,\r\n onFinish: this.props.inReadingTime\r\n ? this.props.clearReadingTime\r\n : this.props.nextPaperPartName\r\n ? this.paperPartTimedOut\r\n : this.timesUp,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n get RightContent() {\r\n const { hasDownload, resourceUrl, hasCalculator, hasWorkings } =\r\n this.props;\r\n\r\n if (hasDownload) {\r\n return this.UploadPanel;\r\n } else if (this.isPdfResourceViewable) {\r\n return this.CachedPdfResourcePanel;\r\n } else if (hasWorkings || hasCalculator) {\r\n return this.WorkingsPanel;\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n get UploadPanel() {\r\n const props = {\r\n formRunGuid: this.props.formRunGuid,\r\n accepts: this.props.uploadFileTypesAllowed,\r\n instantUpload: true,\r\n };\r\n\r\n return {\r\n Icon: UploadIcon,\r\n content: ,\r\n };\r\n }\r\n\r\n get CachedPdfResourcePanel() {\r\n const { resourceUrl } = this.props;\r\n\r\n return {\r\n Icon: ImportContactsIcon,\r\n prioritize: true,\r\n content: (\r\n \r\n ),\r\n };\r\n }\r\n\r\n get WorkingsPanel() {\r\n const props = {\r\n key: this.props.questionId,\r\n disableCalculator: !this.props.hasCalculator,\r\n };\r\n\r\n return {\r\n Icon: BuildIcon,\r\n content: ,\r\n };\r\n }\r\n\r\n get DownloadResourceButton() {\r\n if (!this.isDownloadableResource) {\r\n return null;\r\n }\r\n\r\n const { resourceUrl } = this.props;\r\n\r\n return (\r\n window.open(resourceUrl, \"_blank\")}>\r\n \r\n \r\n );\r\n }\r\n\r\n get PrintResourceButton() {\r\n if (!this.isPrintableResource) {\r\n return null;\r\n }\r\n\r\n const { questionId } = this.props;\r\n const props = { questionId };\r\n\r\n return ;\r\n }\r\n\r\n get StartOrFinishButton() {\r\n const { inReadingTime, bypassReadingTime, clearReadingTime } =\r\n this.props;\r\n\r\n if (inReadingTime) {\r\n if (bypassReadingTime) {\r\n return ;\r\n }\r\n\r\n return null;\r\n } else {\r\n const { practiceExam, nextPaperPartName } = this.props;\r\n const { EXAM_FINISH_CONFIRM: finishExam } = POPUP_TYPES;\r\n const { PART_FINISH_CONFIRM: finishPart } = POPUP_TYPES;\r\n const { PRACTICE_EXAM_FINISH_CONFIRM: finishPractice } =\r\n POPUP_TYPES;\r\n\r\n const onClick = practiceExam\r\n ? this.openPopup(finishPractice)\r\n : nextPaperPartName\r\n ? this.openPopup(finishPart)\r\n : this.openPopup(finishExam);\r\n\r\n return ;\r\n }\r\n }\r\n\r\n get isDownloadableResource() {\r\n const { resourceUrl, questionType } = this.props;\r\n\r\n return (\r\n resourceUrl &&\r\n (questionType === STATIC_TEXT || !doesEndWith(resourceUrl, \"pdf\"))\r\n );\r\n //return resourceUrl && (questionType === STATIC_TEXT || !resourceUrl.endsWith('pdf'));\r\n }\r\n\r\n get isPrintableResource() {\r\n const { questionType, orgID } = this.props;\r\n const orgOK = orgID === 4 || orgID === 12;\r\n\r\n return questionType === STATIC_TEXT && orgOK;\r\n }\r\n\r\n get isPdfResourceViewable() {\r\n const { resourceUrl, questionType } = this.props;\r\n\r\n return (\r\n resourceUrl &&\r\n questionType !== STATIC_TEXT &&\r\n doesEndWith(resourceUrl, \"pdf\")\r\n );\r\n //return resourceUrl && (questionType !== STATIC_TEXT && resourceUrl.endsWith('pdf'));\r\n //return resourceUrl && resourceUrl.endsWith('pdf');\r\n }\r\n\r\n get isPdfResource() {\r\n const { resourceUrl } = this.props;\r\n\r\n return resourceUrl && doesEndWith(resourceUrl, \"pdf\");\r\n //return resourceUrl && resourceUrl.endsWith('pdf');\r\n }\r\n}\r\n\r\nExamQuestionPage.propTypes = {\r\n hasUnansweredQuestions: PropTypes.bool.isRequired,\r\n onShowInstructions: PropTypes.func.isRequired,\r\n onQuestionChange: PropTypes.func.isRequired,\r\n onFinish: PropTypes.func.isRequired,\r\n resourceUrl: PropTypes.string,\r\n hasCalculator: PropTypes.bool.isRequired,\r\n hasWorkings: PropTypes.bool.isRequired,\r\n paperPartName: PropTypes.string,\r\n nextPaperPartName: PropTypes.string,\r\n inReadingTime: PropTypes.bool.isRequired,\r\n practiceExam: PropTypes.bool.isRequired,\r\n clearReadingTime: PropTypes.func.isRequired,\r\n};\r\n\r\n// ExamQuestionPage (connected to store)\r\n// -----------------------------------------------\r\n\r\nconst areThereUnansweredQuestions = (contentData, paperPartId) => {\r\n if (!paperPartId) {\r\n return hasUnansweredQuestions(contentData);\r\n }\r\n return hasPaperPartUnansweredQuestions(contentData)(paperPartId);\r\n};\r\n\r\nconst mapStoreToProps = (store, ownProps) => {\r\n const examData = getExamData(store);\r\n const contentData = getContentData(examData);\r\n const questionId = parseInt(ownProps.questionId, 10);\r\n const paperPartId = getPaperPartId(contentData)(questionId);\r\n const nextPaperPartId =\r\n paperPartId && getNextPaperPartId(contentData)(paperPartId);\r\n const inReadingTime = getReadingTimeRemaining(examData, questionId) > 0;\r\n const bypassReadingTime =\r\n inReadingTime && canBypassReadingTime(getTimeData(examData));\r\n\r\n const sectionsData = getSectionsData(contentData);\r\n const paperPart =\r\n paperPartId && getSectionDataById(sectionsData)(paperPartId);\r\n const paperPartName = paperPart && getSectionName(paperPart);\r\n\r\n const nextPaperPart =\r\n nextPaperPartId && getSectionDataById(sectionsData)(nextPaperPartId);\r\n const nextPaperPartName = nextPaperPart && getSectionName(nextPaperPart);\r\n\r\n const questionsData = getQuestionsData(contentData);\r\n const questionData = getQuestionDataById(questionsData)(questionId);\r\n const questionType = getType(questionData);\r\n const hasDownload = questionType === ICT;\r\n\r\n const formRunGuid = getFormRunGuid(examData);\r\n const settingsData = getSettingsData(store);\r\n const clientData = getClientSettingsData(settingsData);\r\n const uploadFileTypesAllowed = getUploadFileTypesAllowed(clientData);\r\n\r\n const sessionData = getSessionData(store);\r\n const clientSessionData = getClientSessionData(sessionData);\r\n const orgID = getOrgId(clientSessionData);\r\n\r\n const showExamInFullScreen = getShowExamInFullScreen(examData);\r\n const initialFullScreen = getInitialFullScreen(examData);\r\n const needToCloseFullScreen = showExamInFullScreen && !initialFullScreen;\r\n\r\n const returnObject = {\r\n hasUnansweredQuestions: areThereUnansweredQuestions(\r\n contentData,\r\n paperPartId\r\n ),\r\n hasCalculator: requiresCalculator(contentData, questionId),\r\n hasWorkings: requiresWorkings(contentData, questionId),\r\n resourceUrl: getResourceUrl(contentData),\r\n practiceExam: isPracticeExam(examData),\r\n inReadingTime,\r\n bypassReadingTime,\r\n questionId,\r\n\r\n // used by 'mergeProps()'\r\n nextPartQuestionId:\r\n nextPaperPartId &&\r\n getFirstQuestionIdInSection(contentData)(nextPaperPartId),\r\n paperPartId,\r\n paperPartName,\r\n nextPaperPartName,\r\n questionType,\r\n orgID,\r\n hasDownload,\r\n needToCloseFullScreen,\r\n };\r\n\r\n if (hasDownload) {\r\n returnObject.formRunGuid = formRunGuid;\r\n returnObject.uploadFileTypesAllowed = uploadFileTypesAllowed;\r\n }\r\n\r\n return returnObject;\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({ dispatch });\r\n\r\nconst mergeProps = (storeProps, { dispatch }, ownProps) => {\r\n const { paperPartId, nextPartQuestionId } = storeProps;\r\n\r\n const clearReadingTime = paperPartId\r\n ? () => dispatch(setSectionReadingTimeRemaining(0, paperPartId))\r\n : () => dispatch(setReadingTimeRemaining(0));\r\n\r\n const onFinish = !nextPartQuestionId\r\n ? ownProps.onFinish\r\n : () => {\r\n dispatch(setPaperPartComplete(paperPartId));\r\n ownProps.onQuestionChange(nextPartQuestionId);\r\n };\r\n\r\n return {\r\n ...ownProps,\r\n ...storeProps,\r\n clearReadingTime,\r\n onFinish: onFinish,\r\n currentPaperPartId: paperPartId,\r\n paperPartId: undefined,\r\n nextPartQuestionId: undefined,\r\n };\r\n};\r\n\r\nconst args = [mapStoreToProps, mapDispatchToProps, mergeProps];\r\nExamQuestionPage = connect(...args)(ExamQuestionPage);\r\n\r\nExamQuestionPage.propTypes = {\r\n onQuestionChange: PropTypes.func.isRequired,\r\n onFinish: PropTypes.func.isRequired,\r\n onShowInstructions: PropTypes.func.isRequired,\r\n questionId: PropTypes.string.isRequired,\r\n};\r\n\r\n// EXPORT\r\n// --------------------------------------------------------------------\r\nexport { ExamQuestionPage };\r\n","\r\n// npm\r\nimport check from 'check-types'\r\n\r\n\r\n// \r\n// See first: https://github.com/ReactTraining/history\r\n//\r\n// \r\n// Note:\r\n//\r\n// It's only possible to attach one 'block' listener to react-router's history\r\n// object, however we need to manage several of them across the app.\r\n//\r\n// So in order to do this, we need one listener that can call a bunch of other\r\n// listeners every time a route transition has taken place.\r\n// \r\n// I made this RoutingBlocker to do that. Listeners are added via 'subscribe,'\r\n// and whenever a transition happens they will be called in reverse order until\r\n// a successful blocking value is found.\r\n\r\n\r\n// Note #2:\r\n//\r\n// There are several ways we can block a route transition. These are:\r\n// \r\n// BLOCK: Show alert() and outright block transition\r\n// CONFIRM: Show confirm() and block transition if user doesn't accept\r\n// NOTIFY: Show alert() and continue the transition as normal\r\n// \r\n// As before, it's only possible to set this up in one place (namely in\r\n// ). Typically, receives a string message\r\n// to display to the user as a popup when a transition should be blocked.\r\n//\r\n// In our case, we will need to encode the 'type' of popup into that string as\r\n// well.\r\n//\r\n// With that in mind, any listeners subscribing to this class must return an \r\n// object {type, message} rather than only the message (as was mandated in the\r\n// history docs).\r\n//\r\n// This class will then handle encoding the type into the final message, which\r\n// is then received by to determine the kind of popup to show.\r\n\r\n\r\nconst BLOCKING_TYPES = {\r\n\tBLOCK: \"block.navigation.completely\",\r\n\tCONFIRM: \"navigation.requires.user.confirmation\",\r\n\tNOTIFY: \"navigation.proceeds.with.alert\"\r\n}\r\n\r\nconst blockingTypes = Object.values(BLOCKING_TYPES);\r\n\r\n\r\nconst validateBlockingMessage = (blockingMessage) =>\r\n{\r\n\tif (!check.string(blockingMessage)) {\r\n\t\tthrow \"RoutingBlocker: 'blockingMessage' is not a string\";\r\n\t}\r\n}\r\n\r\nconst validateSetTransition = (setTransition) =>\r\n{\r\n\tif (!check.function(setTransition)) {\r\n\t\tthrow \"RoutingBlocker: 'setTransition' is not a function\";\r\n\t}\r\n}\r\n\r\nconst validateBlockingValue = (blockingValue) =>\r\n{\r\n\tif (!check.object(blockingValue)) {\r\n\t\tthrow \"RoutingBlocker: 'blockingValue' is not an object\";\r\n\t}\r\n\r\n\tif (!blockingTypes.includes(blockingValue.type)) {\r\n\t\tthrow \"RoutingBlocker: 'blockingValue' doesn't contain a valid blocking 'type'\";\r\n\t}\r\n\r\n\tif (!check.string(blockingValue.message)) {\r\n\t\tthrow \"RoutingBlocker: 'blockingValue' doesn't contain a string 'message'\";\r\n\t}\r\n}\r\n\r\nconst getPopup = (blockingMessage, setTransition) =>\r\n{\r\n\tvalidateBlockingMessage(blockingMessage);\r\n\tvalidateSetTransition(setTransition);\r\n\r\n\tif (blockingMessage.startsWith(BLOCKING_TYPES.BLOCK)) {\r\n\t\tconst finalMessage = blockingMessage.substring(BLOCKING_TYPES.BLOCK.length);\r\n\t\twindow.alert(finalMessage);\r\n\t\tsetTransition(false);\r\n\t}\r\n\telse if (blockingMessage.startsWith(BLOCKING_TYPES.CONFIRM)) {\r\n\t\tconst finalMessage = blockingMessage.substring(BLOCKING_TYPES.CONFIRM.length);\r\n\t\tsetTransition(window.confirm(finalMessage));\r\n\t}\r\n\telse if (blockingMessage.startsWith(BLOCKING_TYPES.NOTIFY)) {\r\n\t\tconst finalMessage = blockingMessage.substring(BLOCKING_TYPES.NOTIFY.length);\r\n\t\twindow.alert(finalMessage);\r\n\t\tsetTransition(true);\r\n\t}\r\n\telse {\r\n\t\tsetTransition(false);\r\n\t}\r\n}\r\n\r\n\r\nclass RoutingBlocker\r\n{\r\n\tconstructor()\r\n\t{\r\n\t\tconst blockingFunctions = [];\r\n\r\n\t\tthis.block = (history) => {\r\n\t\t\treturn history.block((location, action) => {\r\n\t\t\t\tfor (let i = blockingFunctions.length - 1; i >= 0; i--) {\r\n\t\t\t\t\tconst blockingValue = blockingFunctions[i](location, action);\r\n\t\t\t\t\tif (blockingValue) {\r\n\t\t\t\t\t\tvalidateBlockingValue(blockingValue);\r\n\t\t\t\t\t\treturn blockingValue.type + blockingValue.message;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\r\n\t\tconst unsubscribe = (blockingFunction) => () => {\r\n\t\t\tconst index = blockingFunctions.indexOf(blockingFunction);\r\n\t\t\tif (index < 0) { return; }\r\n\t\t\tblockingFunctions.splice(index, 1);\r\n\t\t}\r\n\r\n\t\tthis.subscribe = (blockingFunction) => {\r\n\t\t\tblockingFunctions.push(blockingFunction);\r\n\t\t\treturn unsubscribe(blockingFunction);\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\r\nconst routingBlocker = new RoutingBlocker();\r\n\r\n\r\nexport {routingBlocker, getPopup, BLOCKING_TYPES}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport {Switch, Route, withRouter} from 'react-router-dom'\r\n\r\n// react\r\nimport {ExamQuestionPage} from './page'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getContentData} from 'redux/reducers/exam/selectors'\r\nimport {getSectionsData} from 'redux/reducers/exam/content/selectors'\r\nimport {getPaperPartId} from 'redux/reducers/exam/content/complex-selectors'\r\nimport {getSectionDataById} from 'redux/reducers/exam/content/sections/selectors'\r\nimport {isComplete} from 'redux/reducers/exam/content/sections/section/selectors'\r\n\r\n\r\n// redux (actions)\r\nimport {setCurrentQuestionId as setCurrentQuestionIdAction} from './actions'\r\n\r\n// constants\r\nimport {routingBlocker, BLOCKING_TYPES} from 'utils/routing-blocker'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\n// ExamQuestionsPageRouter (not connected to store/router)\r\n// ------------------------------------------------------------------------\r\n\r\nclass ExamQuestionsPageRouter extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.createQuestionPage = ({match}) => {\r\n\t\t\tconst {guid, questionId} = match.params;\r\n\t\t\tconst {history, onShowInstructions, setCurrentQuestionId} = this.props;\r\n\r\n\t\t\tconst pageProps = {\r\n\t\t\t\tonQuestionChange: (id) => {\r\n\t\t\t\t\tsetCurrentQuestionId(id);\r\n\t\t\t\t\thistory.push(`/exam/${guid}/${id}`)\r\n\t\t\t\t},\r\n\t\t\t\tonFinish: () => history.push(`/exam/${guid}/outro`),\r\n\t\t\t\tonShowInstructions,\r\n\t\t\t\tquestionId\r\n\t\t\t}\r\n\r\n\t\t\treturn ;\r\n\t\t}\r\n\r\n\t\tconst getQuestionIdFromLocation = ({pathname}) => {\r\n\t\t\tconst lastForwardSlash = pathname.lastIndexOf('/');\r\n\t\t\tconst questionId = pathname.substring(lastForwardSlash + 1);\r\n\t\t\treturn parseInt(questionId, 10);\r\n\t\t}\r\n\r\n\t\tthis.blockInvalidRoutingMessage = (questionId) => {\r\n\t\t\tif (this.props.isValidQuestion(questionId)) { return; }\r\n\t\t\treturn {\r\n\t\t\t\ttype: BLOCKING_TYPES.BLOCK,\r\n\t\t\t\tmessage: this.props.messages[MESSAGE_IDS.EXAM.PAPER_PART_NAV_INTERRUPTED]\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.unsubscribe = routingBlocker.subscribe((location, action) => {\r\n\t\t\tif (action === \"PUSH\" || action === \"REPLACE\") { return; }\t\t\t\t\t\t\t\t\t// PUSH/REPLACE happens in 'onQuestionChange'. That function will always navigate to a valid question & already handles updating the store\r\n\t\t\tif (!location.pathname.includes('exam')) { return; }\r\n\r\n\t\t\tconst questionId = getQuestionIdFromLocation(location);\r\n\t\t\tconst blockMessage = this.blockInvalidRoutingMessage(questionId);\r\n\t\t\tif (!blockMessage) { this.props.setCurrentQuestionId(questionId); }\r\n\t\t\treturn blockMessage;\r\n\t\t})\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t \"Sorry, you just experienced a routing error. Please note the current url in your browser.\"}/>\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tthis.unsubscribe();\r\n\t}\r\n}\r\n\r\nExamQuestionsPageRouter.propTypes = {\r\n\tlocation: PropTypes.object.isRequired,\r\n\thistory: PropTypes.object.isRequired,\r\n\tonShowInstructions: PropTypes.func.isRequired,\r\n\tsetCurrentQuestionId: PropTypes.func.isRequired,\r\n\tisValidQuestion: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.object.isRequired\r\n}\r\n\r\n\r\n// ExamQuestionsPageRouter (connected to store/router)\r\n// ------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\tisValidQuestion: (id) => {\r\n\t\tconst contentData = getContentData(getExamData(store));\r\n\t\tconst paperPartId = getPaperPartId(contentData)(id);\r\n\t\tif (!paperPartId) { return true; }\r\n\r\n\t\tconst sectionsData = getSectionsData(contentData);\r\n\t\tconst partData = getSectionDataById(sectionsData)(paperPartId);\r\n\t\treturn !isComplete(partData);\r\n\t}\r\n})\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tsetCurrentQuestionId: (id) => dispatch(setCurrentQuestionIdAction(id))\r\n})\r\n\r\nExamQuestionsPageRouter = connect(mapStoreToProps, mapDispatchToProps)(ExamQuestionsPageRouter);\r\nExamQuestionsPageRouter = withRouter(ExamQuestionsPageRouter);\r\nExamQuestionsPageRouter = withMessages(ExamQuestionsPageRouter);\r\n\r\n\r\n// ExamQuestionsPageRouter (EXPORT)\r\n// ------------------------------------------------------------------------\r\nexport {ExamQuestionsPageRouter}","\r\n// xams-utils\r\nimport {IdGenerator} from '@xams-utils/id-generator'\r\n\r\n\r\nconst STACK_TYPES = {\r\n\tFIFO: 0,\r\n\tLIFO: 1\r\n}\r\n\r\n\r\n// [BACK, ..., ..., ..., FRONT]\r\n// next -> <- previous \r\n\r\n// On pop()...\r\n// FIFO = remove BACK\r\n// LIFO = remove FRONT\r\n\r\n\r\nclass StackItem\r\n{\r\n\tconstructor(id, item)\r\n\t{\r\n\t\tthis.id = id;\r\n\t\tthis.item = item;\r\n\t\tthis.nextId = null;\r\n\t\tthis.previousId = null;\r\n\t}\r\n\r\n\tset NextId(id)\r\n\t{\r\n\t\tthis.nextId = id;\r\n\t}\r\n\r\n\tset PreviousId(id)\r\n\t{\r\n\t\tthis.previousId = id;\r\n\t}\r\n}\r\n\r\n\r\nclass Stack\r\n{\r\n\tconstructor(stackType=STACK_TYPES.FIFO)\r\n\t{\r\n\t\tthis.items = {};\r\n\t\tthis.type = stackType;\r\n\r\n\t\tthis.frontId = null;\r\n\t\tthis.backId = null;\r\n\t}\r\n\r\n\tpush(item, id=IdGenerator.uuid())\r\n\t{\r\n\t\tthis.validateNewId(id);\r\n\r\n\t\tconst newItem = new StackItem(id, item);\r\n\t\tconst frontItem = this.FrontItem;\r\n\r\n\t\tif (frontItem) {\r\n\t\t\tthis.createReferences(frontItem, newItem);\r\n\t\t}\r\n\r\n\t\tthis.tryUpdateBackItem(id);\r\n\t\tthis.frontId = id;\r\n\t\tthis.items[id] = newItem;\r\n\r\n\t\treturn id;\r\n\t}\r\n\r\n\tvalidateNewId(id)\r\n\t{\r\n\t\tif (this.hasItemWithId(id)) {\r\n\t\t\tthrow `Stack already contains an element with the id: ${id}`;\r\n\t\t}\r\n\t}\r\n\r\n\ttryUpdateBackItem(id)\r\n\t{\r\n\t\tif (this.backId === null) {\r\n\t\t\tthis.backId = id;\r\n\t\t}\r\n\t}\r\n\r\n\tpop()\r\n\t{\r\n\t\tconst isFifo = this.type === STACK_TYPES.FIFO;\r\n\t\treturn isFifo ? this.popItem(false) : this.popItem(true);\r\n\t}\r\n\r\n\tpopItem(fromFront)\r\n\t{\r\n\t\tconst popItemId = fromFront ? this.frontId : this.backId;\r\n\t\tif (!popItemId) { return undefined; }\r\n\r\n\t\tconst itemToPop = this.getStackItem(popItemId);\r\n\t\tconst neighbourId = itemToPop[fromFront ? 'previousId' : 'nextId'];\r\n\r\n\t\tif (neighbourId) {\r\n\t\t\tconst neighbourItem = this.getStackItem(neighbourId);\r\n\t\t\tif (fromFront) {\r\n\t\t\t\tthis.removeReferences(neighbourItem, itemToPop);\r\n\t\t\t\tthis.frontId = neighbourId;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tthis.removeReferences(itemToPop, neighbourItem);\r\n\t\t\t\tthis.backId = neighbourId;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse {\r\n\t\t\tthis.backId = null;\r\n\t\t\tthis.frontId = null;\r\n\t\t}\r\n\r\n\t\tdelete this.items[popItemId];\r\n\t\treturn itemToPop.item;\r\n\t}\r\n\r\n\tcreateReferences(/*stack items*/)\r\n\t{\r\n\t\tfor (let i = 1; i < arguments.length; i++) {\r\n\t\t\tconst lhsItem = arguments[i - 1];\r\n\t\t\tconst rhsItem = arguments[i];\r\n\t\t\tlhsItem.NextId = rhsItem.id;\r\n\t\t\trhsItem.PreviousId = lhsItem.id;\r\n\t\t}\r\n\t}\r\n\r\n\tremoveReferences(/*stack items*/)\r\n\t{\r\n\t\tfor (let i = 1; i < arguments.length; i++) {\r\n\t\t\tconst lhsItem = arguments[i - 1];\r\n\t\t\tconst rhsItem = arguments[i];\r\n\t\t\tlhsItem.NextId = null;\r\n\t\t\trhsItem.PreviousId = null;\r\n\t\t}\r\n\t}\r\n\r\n\tget FrontItem()\r\n\t{\r\n\t\tif (this.frontId !== null) {\r\n\t\t\treturn this.getStackItem(this.frontId);\r\n\t\t}\r\n\t}\r\n\r\n\tget BackItem()\r\n\t{\r\n\t\tif (this.backId !== null) {\r\n\t\t\treturn this.getStackItem(this.backId);\r\n\t\t}\r\n\t}\r\n\r\n\tgetStackItem(id)\r\n\t{\r\n\t\treturn this.items[id];\r\n\t}\r\n\r\n\thasItemWithId(id)\r\n\t{\r\n\t\treturn this.items.hasOwnProperty(id);\r\n\t}\r\n\r\n\t*generateElements()\r\n\t{\r\n\t\tlet currentItem = this.BackItem;\r\n\t\tif (!currentItem) { return; }\r\n\t\tyield currentItem.item;\r\n\r\n\t\twhile (currentItem.nextId) {\r\n\t\t\tcurrentItem = this.getStackItem(currentItem.nextId);\r\n\t\t\tyield currentItem.item;\r\n\t\t}\r\n\t}\r\n\r\n\ttoArray()\r\n\t{\r\n\t\tconst array = [];\r\n\r\n\t\tfor (let element in this.generateElements()) {\r\n\t\t\tarray.push(element);\r\n\t\t}\r\n\r\n\t\treturn array;\r\n\t}\r\n\r\n\r\n\tstatic FromArray(array, stackType)\r\n\t{\r\n\t\tconst stack = new Stack(stackType);\r\n\r\n\t\tfor (let i = 0; i < array.length; i++) {\r\n\t\t\tstack.push(array[i]);\r\n\t\t}\r\n\r\n\t\treturn stack;\r\n\t}\r\n\r\n\tstatic FromStack(stackToCopy)\r\n\t{\r\n\t\tconst stack = new Stack(stackToCopy.type);\r\n\t\tlet currentItemId = stackToCopy.backId;\r\n\r\n\t\tif (currentItemId === null) {\r\n\t\t\treturn stack;\r\n\t\t}\r\n\r\n\t\tconst stackItems = stackToCopy.items;\r\n\t\twhile (currentItemId) {\r\n\t\t\tconst currentStackItem = stackItems[currentItemId];\r\n\t\t\tstack.push(currentStackItem.item, currentStackItem.id);\r\n\t\t\tcurrentItemId = currentStackItem.nextId;\r\n\t\t}\r\n\r\n\t\tif (stack.frontId !== stackToCopy.frontId ||\r\n\t\t\t\tstack.backId !== stackToCopy.backId) {\r\n\t\t\tthrow \"Failed to create Stack from Stack\";\r\n\t\t}\r\n\r\n\t\treturn stack;\r\n\t}\r\n}\r\n\r\n\r\nexport {Stack, STACK_TYPES}","// internal\r\nimport { localStorageApi, KEYS } from \"../local-storage-api\";\r\n\r\nclass StoredUsers {\r\n constructor() {\r\n const users = localStorageApi.retrieveDataFrom(KEYS.USERS);\r\n\r\n const save = () => {\r\n localStorageApi.saveDataTo(KEYS.USERS, users);\r\n };\r\n\r\n this.set = (guid, userData) => {\r\n if (users.hasOwnProperty(guid)) {\r\n return;\r\n }\r\n users[guid] = userData;\r\n save();\r\n };\r\n\r\n this.remove = (guid) => {\r\n if (!users.hasOwnProperty(guid)) {\r\n return;\r\n }\r\n delete users[guid];\r\n save();\r\n };\r\n\r\n this.removeAll = () => {\r\n Object.keys(users).forEach((id) => {\r\n delete users[id];\r\n });\r\n save();\r\n };\r\n\r\n this.dangerously_getUsers = () => {\r\n return users;\r\n };\r\n }\r\n}\r\n\r\nconst storedUsers = new StoredUsers();\r\nexport { storedUsers };\r\n","// internal\r\nimport { localStorageApi, KEYS } from \"../local-storage-api\";\r\n\r\nclass StoredExams {\r\n constructor() {\r\n const exams = localStorageApi.retrieveDataFrom(KEYS.EXAMS);\r\n\r\n const save = () => {\r\n localStorageApi.saveDataTo(KEYS.EXAMS, exams);\r\n };\r\n\r\n this.set = (guid, examData) => {\r\n if (exams.hasOwnProperty(guid)) {\r\n return;\r\n }\r\n exams[guid] = examData;\r\n save();\r\n };\r\n\r\n this.remove = (guid) => {\r\n if (!exams.hasOwnProperty(guid)) {\r\n return;\r\n }\r\n delete exams[guid];\r\n save();\r\n };\r\n\r\n this.removeAll = () => {\r\n Object.keys(exams).forEach((id) => {\r\n delete exams[id];\r\n });\r\n save();\r\n };\r\n\r\n this.dangerously_getUsers = () => {\r\n return exams;\r\n };\r\n }\r\n}\r\n\r\nconst storedExams = new StoredExams();\r\nexport { storedExams };\r\n","// internal\r\nimport { localStorageApi, KEYS } from \"../local-storage-api\";\r\n\r\nclass RequestLogMappings {\r\n constructor() {\r\n const mappings = localStorageApi.retrieveDataFrom(\r\n KEYS.REQUEST_LOG_MAPPINGS\r\n );\r\n\r\n const saveToLocalStorage = () => {\r\n try{\r\n localStorageApi.saveDataTo(KEYS.REQUEST_LOG_MAPPINGS, mappings);\r\n return true;\r\n }\r\n catch(e){\r\n return false;\r\n }\r\n };\r\n\r\n const getStaticData = (requestLog) => ({\r\n referenceCount: 0,\r\n type: requestLog.type,\r\n payload: requestLog.payload,\r\n endpoint: requestLog.endpoint,\r\n userGuid: requestLog.userGuid,\r\n examGuid: requestLog.examGuid,\r\n });\r\n\r\n this.createMapping = (id, requestLog) => {\r\n const mapping = mappings[id] || getStaticData(requestLog);\r\n mapping.referenceCount = mapping.referenceCount + 1;\r\n mappings[id] = mapping;\r\n saveToLocalStorage();\r\n };\r\n\r\n this.removeMappingReference = (id, saveIt = true) => {\r\n const mapping = mappings[id];\r\n if (!mapping) {\r\n //throw \"Can't remove non-existent mapping\";\r\n return;\r\n }\r\n\r\n mapping.referenceCount = mapping.referenceCount - 1;\r\n if (mapping.referenceCount <= 0) {\r\n delete mappings[id];\r\n }\r\n\r\n if (saveIt) {\r\n saveToLocalStorage();\r\n }\r\n };\r\n\r\n this.save = () => {\r\n return saveToLocalStorage();\r\n }\r\n\r\n this.removeAll = () => {\r\n Object.keys(mappings).forEach((id) => {\r\n delete mappings[id];\r\n });\r\n saveToLocalStorage();\r\n };\r\n\r\n this.getMapping = (id) => {\r\n return mappings[id];\r\n };\r\n\r\n this.containsUser = (guid) => {\r\n return !!Object.values(mappings).find((mapping) => {\r\n return mapping.userGuid === guid;\r\n });\r\n };\r\n\r\n this.containsExam = (guid) => {\r\n return !!Object.values(mappings).find((mapping) => {\r\n return mapping.examGuid === guid;\r\n });\r\n };\r\n\r\n this.dangerously_getMappings = () => {\r\n return mappings;\r\n };\r\n }\r\n}\r\n\r\nconst requestLogMappings = new RequestLogMappings();\r\nexport { requestLogMappings };\r\n","// internal\r\nimport { storedUsers } from \"./users\";\r\nimport { storedExams } from \"./exams\";\r\nimport { requestLogMappings } from \"./request-log-mappings\";\r\nimport { localStorageApi, KEYS } from \"../local-storage-api\";\r\n\r\n// xams-utils\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\n// custom\r\nimport { Stack } from \"custom/stack\";\r\nimport moment from \"moment\";\r\n\r\n//import {tempShowLocalStorage, tempUpdateLocalStorage, showLogDate} from './network-request-log-test'\r\n\r\nconst MAX_POPS = 10;\r\n\r\nconst dateHasExpired = (item, expired = 30) => {\r\n if (!item) return false;\r\n\r\n const timeStamp = item.item ? item.item.timeStamp : item.timeStamp;\r\n\r\n if (!timeStamp) return false;\r\n\r\n const date = moment.utc(timeStamp);\r\n const expiredDate = moment().subtract(expired, \"days\");\r\n\r\n return date < expiredDate;\r\n};\r\n\r\nconst hasRequestLogGotActivities = (requestLog) => {\r\n if (!requestLog.payload) return false;\r\n if (!requestLog.endpoint) return false;\r\n\r\n return (\r\n requestLog.payload.activities &&\r\n requestLog.endpoint.indexOf(\"activitylog\")!==-1\r\n );\r\n};\r\n\r\nconst removeActivitiesFromRequestLog = (_requestLog) => {\r\n const requestLog = {};\r\n\r\n Object.keys(_requestLog).forEach(key => {\r\n if (key === 'payload') {\r\n const payload = {};\r\n Object.keys(_requestLog.payload).forEach(key => {\r\n if (key !=='activities') payload[key] = _requestLog.payload[key];\r\n });\r\n requestLog.payload = payload;\r\n }\r\n else requestLog[key] = _requestLog[key];\r\n });\r\n\r\n return {...requestLog};\r\n};\r\n\r\nclass NetworkRequestLog {\r\n constructor() {\r\n const logs = localStorageApi.retrieveDataFrom(KEYS.NETWORK_REQUEST_LOG);\r\n let logStack = check.nonEmptyObject(logs)\r\n ? Stack.FromStack(logs)\r\n : new Stack();\r\n const initialized = { value: false };\r\n\r\n const initialize = () => {\r\n const lastItem = logStack.BackItem;\r\n const firstItem = logStack.FrontItem;\r\n\r\n if (dateHasExpired(firstItem)) {\r\n removeAllLogs();\r\n } else if (dateHasExpired(lastItem)) {\r\n removeExpiredLogs();\r\n }\r\n\r\n initialized.value = true;\r\n };\r\n\r\n const removeAllLogs = () => {\r\n logStack = new Stack();\r\n saveToLocalStorage();\r\n\r\n requestLogMappings.removeAll();\r\n storedExams.removeAll();\r\n storedUsers.removeAll();\r\n };\r\n\r\n const removeExpiredLogs = () => {\r\n let poppedItems = [];\r\n\r\n while (true) {\r\n const lastItem = logStack.BackItem;\r\n if (!lastItem) break;\r\n if (dateHasExpired(lastItem)) {\r\n const poppedItem = logStack.pop();\r\n if (!poppedItem) break;\r\n poppedItems = poppedItems.concat([poppedItem]);\r\n } else {\r\n break;\r\n }\r\n }\r\n\r\n if (poppedItems.length > 0) {\r\n saveToLocalStorage();\r\n\r\n let userGuids = [];\r\n let examGuids = [];\r\n\r\n poppedItems.forEach((item) => {\r\n requestLogMappings.removeMappingReference(item.id, false);\r\n\r\n if (userGuids.indexOf(item.userGuid) === -1) {\r\n userGuids = userGuids.concat([item.userGuid]);\r\n }\r\n if (userGuids.indexOf(item.examGuid) === -1) {\r\n examGuids = examGuids.concat([item.examGuid]);\r\n }\r\n });\r\n\r\n requestLogMappings.save();\r\n\r\n userGuids.forEach((userGuid) => {\r\n tryRemoveAssociatedUser(userGuid);\r\n });\r\n\r\n examGuids.forEach((examGuid) => {\r\n tryRemoveAssociatedExam(examGuid);\r\n });\r\n }\r\n };\r\n\r\n const tryRemoveAssociatedExam = (guid) => {\r\n if (!guid) {\r\n return;\r\n }\r\n if (!requestLogMappings.containsExam(guid)) {\r\n storedExams.remove(guid);\r\n }\r\n };\r\n\r\n const tryRemoveAssociatedUser = (guid) => {\r\n if (!guid) {\r\n return;\r\n }\r\n if (!requestLogMappings.containsUser(guid)) {\r\n storedUsers.remove(guid);\r\n }\r\n };\r\n\r\n const removeExcessLogs = (doubleIt = false) => {\r\n let counter = 0;\r\n const counterMax = 100;\r\n let poppedItems = [];\r\n const max = doubleIt ? MAX_POPS * 2 : MAX_POPS;\r\n\r\n while (true) {\r\n\r\n while (true) {\r\n for (let i = 0; i < max; i++) {\r\n const poppedItem = logStack.pop();\r\n if (poppedItem) {\r\n poppedItems = poppedItems.concat([poppedItem]);\r\n } else break;\r\n }\r\n try {\r\n localStorageApi.saveDataTo(\r\n KEYS.NETWORK_REQUEST_LOG,\r\n logStack\r\n );\r\n break;\r\n } catch (e) {}\r\n }\r\n\r\n poppedItems.forEach((poppedItem) => {\r\n requestLogMappings.removeMappingReference(poppedItem.id, false);\r\n tryRemoveAssociatedUser(poppedItem.userGuid);\r\n tryRemoveAssociatedExam(poppedItem.examGuid);\r\n });\r\n\r\n if (requestLogMappings.save()){\r\n break;\r\n }\r\n\r\n counter++;\r\n if (counter > counterMax) {\r\n this.removeAllLogs();\r\n break;\r\n }\r\n }\r\n };\r\n\r\n const saveToLocalStorage = () => {\r\n while (true) {\r\n try {\r\n localStorageApi.saveDataTo(\r\n KEYS.NETWORK_REQUEST_LOG,\r\n logStack\r\n );\r\n break;\r\n } catch (e) {\r\n removeExcessLogs();\r\n }\r\n }\r\n };\r\n\r\n this.push = (id, _requestLog) => {\r\n if (!initialized.value) initialize();\r\n\r\n const requestLog = hasRequestLogGotActivities(_requestLog)\r\n ? removeActivitiesFromRequestLog({ ..._requestLog })\r\n : _requestLog;\r\n\r\n while (true) {\r\n try {\r\n requestLogMappings.createMapping(id, requestLog);\r\n break;\r\n } catch (e) {\r\n removeExcessLogs();\r\n }\r\n }\r\n\r\n const { status, response, responseHeaders, timeStamp } = requestLog;\r\n logStack.push({ id, status, response, responseHeaders, timeStamp });\r\n\r\n saveToLocalStorage();\r\n };\r\n\r\n this.dangerously_getLogStack = () => {\r\n return logStack;\r\n };\r\n\r\n this.clearSpace = () => {\r\n removeExcessLogs(true);\r\n };\r\n }\r\n}\r\n\r\nconst networkRequestLog = new NetworkRequestLog();\r\nexport { networkRequestLog };\r\n","\r\n// custom\r\nimport {Stack} from 'custom/stack'\r\n\r\n// xams-utils\r\nimport {check} from '@xams-utils/check-types'\r\n\r\n// utils\r\nimport {networkRequestLog} from 'libs/browser_storage/apis/network-request-log'\r\nimport {requestLogMappings} from 'libs/browser_storage/apis/request-log-mappings'\r\n\r\n\r\n// ---------------------------------------------------------------------------\r\n// PRIVATE FUNCTIONS\r\n// ---------------------------------------------------------------------------\r\n\r\nconst validateQueryType = (query) =>\r\n{\r\n\tif (!check.object(query)) {\r\n\t\tthrow \"queryLogs: query must be an object\";\r\n\t}\r\n\r\n\tconst {userGuid} = query;\r\n\tif (check.assigned(userGuid) && !check.string(userGuid)) {\r\n\t\tthrow \"queryLogs: userGuid must be a valid string\";\r\n\t}\r\n\r\n\tconst {examGuid} = query;\r\n\tif (check.assigned(examGuid) && !check.string(examGuid)) {\r\n\t\tthrow \"findExamRequests: examGuid must be a valid string\";\r\n\t}\r\n\r\n\tconst {minDt} = query;\r\n\tif (check.assigned(minDt) && !check.date(minDt)) {\r\n\t\tthrow \"findExamRequests: minDt must be a valid date\";\r\n\t}\r\n\r\n\tconst {maxDt} = query;\r\n\tif (check.assigned(maxDt) && !check.date(maxDt)) {\r\n\t\tthrow \"findExamRequests: maxDt must be a valid date\";\r\n\t}\r\n}\r\n\r\n\r\nconst matchesUser = (requestData, userGuid) =>\r\n{\r\n\tif (!userGuid) { return true; }\r\n\treturn requestData.userGuid === userGuid;\r\n}\r\n\r\nconst matchesExam = (requestData, examGuid) =>\r\n{\r\n\tif (!examGuid) { return true; }\r\n\treturn requestData.examGuid === examGuid;\r\n}\r\n\r\nconst matchesDate = (request, minDate, maxDate) =>\r\n{\r\n\tif (!minDate && !maxDate) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tminDate = new Date(minDate);\r\n\tmaxDate = new Date(maxDate);\r\n\tconst requestDate = new Date(request.timeStamp);\r\n\r\n\tif (minDate && requestDate < minDate) { return false; }\r\n\tif (maxDate && requestDate > maxDate) { return false; }\r\n\r\n\treturn true;\r\n}\r\n\r\n\r\nconst queryLogs = function*(query)\r\n{\r\n\tvalidateQueryType(query);\r\n\r\n\tfor (let request of getRequestLogStack().generateElements()) {\r\n\t\tconst requestData = requestLogMappings.getMapping(request.id);\r\n\t\tif (!requestData) { continue; }\r\n\r\n\t\tif (matchesUser(requestData, query.userGuid)) {\r\n\t\t\tif (matchesExam(requestData, query.examGuid)) {\r\n\t\t\t\tif (matchesDate(request, query.minDt, query.maxDt)) {\r\n\t\t\t\t\tyield {...request, ...requestData};\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\r\nconst getRequestLogStack = () =>\r\n{\r\n\treturn networkRequestLog.dangerously_getLogStack();\r\n}\r\n\r\n\r\n// ---------------------------------------------------------------------------\r\n// PUBLIC INTERFACE\r\n// ---------------------------------------------------------------------------\r\n\r\nconst findUserActivityByQuery = (query={}) =>\t// returns 'userActivity' data for 'BugReport' api call\r\n{\r\n\tconst userActivity = [];\r\n\tconst userActivityByGuid = {};\r\n\r\n\tfor (let loggedRequest of queryLogs(query)) {\r\n\t\tconst {userGuid} = loggedRequest;\r\n\t\tif (userActivityByGuid.hasOwnProperty(userGuid)) {\r\n\t\t\tvar userActivityItem = userActivityByGuid[userGuid];\r\n\t\t}\r\n\t\telse {\r\n\t\t\tvar userActivityItem = {userGuid, requests: []};\r\n\t\t\tuserActivityByGuid[userGuid] = userActivityItem;\r\n\t\t\tuserActivity.push(userActivityItem);\r\n\t\t}\r\n\r\n\t\tconst {payload} = loggedRequest;\r\n\t\tconst userRequest = {\r\n\t\t\tendpoint: loggedRequest.endpoint,\r\n\t\t\tpayload: payload ? JSON.stringify(payload) : null,\r\n\t\t\ttimeStamp: loggedRequest.timeStamp,\r\n\t\t\tstatus: String(loggedRequest.status),\r\n\t\t\tresponse: loggedRequest.response,\r\n\t\t\tresponseHeaders: loggedRequest.responseHeaders\r\n\t\t};\r\n\r\n\t\tuserActivityItem.requests.push(userRequest);\r\n\t}\r\n\r\n\treturn userActivity;\r\n}\r\n\r\nconst findUserGuidsByQuery = (query={}) =>\r\n{\r\n\tconst uniqueUserGuids = new Set();\r\n\r\n\tfor (let loggedRequest of queryLogs(query)) {\r\n\t\tuniqueUserGuids.add(loggedRequest.userGuid);\r\n\t}\r\n\r\n\treturn [...uniqueUserGuids];\r\n}\r\n\r\nconst findExamGuidsByQuery = (query={}) =>\r\n{\r\n\tconst uniqueExamGuids = new Set();\r\n\r\n\tfor (let loggedRequest of queryLogs(query)) {\r\n\t\tif (loggedRequest.examGuid) {\r\n\t\t\tuniqueExamGuids.add(loggedRequest.examGuid);\r\n\t\t}\r\n\t}\r\n\r\n\treturn [...uniqueExamGuids];\r\n}\r\n\r\n\r\nexport {\r\n\tfindUserActivityByQuery,\r\n\tfindUserGuidsByQuery,\r\n\tfindExamGuidsByQuery\r\n}","\r\n// internal\r\nimport {localStorageApi, KEYS} from '../local-storage-api'\r\n\r\n// utils\r\nimport {errorApi} from 'libs/api/interface/api-error'\r\nimport {findUserActivityByQuery} from 'components/pages/layout/app_bar/bug_reporter/popup/query-logs'\r\n\r\n\r\nclass ScheduledBugReports\r\n{\r\n\tconstructor()\r\n\t{\r\n\t\tconst {queue=[]} = localStorageApi.retrieveDataFrom(KEYS.SCHEDULED_BUG_REPORTS);\r\n\r\n\r\n\t\tconst save = () => {\r\n\t\t\tlocalStorageApi.saveDataTo(KEYS.SCHEDULED_BUG_REPORTS, {queue});\r\n\t\t}\r\n\r\n\t\tconst isAlreadyScheduled = (query) => {\r\n\t\t\tconst queryKeys = Object.keys(query);\r\n\r\n\t\t\treturn !!queue.find(report => {\r\n\t\t\t\treturn queryKeys.every(key => query[key] === report.query[key]);\r\n\t\t\t});\r\n\t\t}\r\n\r\n\r\n\t\tthis.schedule = (message, query, reason={}) => {\r\n\t\t\tif (!isAlreadyScheduled(query)) {\r\n\t\t\t\tqueue.push({message, query, reason});\r\n\t\t\t\tsave();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.remove = (report) => {\r\n\t\t\tconst index = queue.indexOf(report);\r\n\t\t\tqueue.splice(index, 1);\r\n\t\t\tsave();\r\n\t\t}\r\n\r\n\t\tthis.processQueue = () => {\r\n\t\t\tqueue.forEach(scheduledReport => {\r\n\t\t\t\tconst payload = {\r\n\t\t\t\t\treporterGuid: \"SYSTEM[V6/PLAYER]\",\r\n\t\t\t\t\tbugDescription: scheduledReport.message,\r\n\t\t\t\t\treason: scheduledReport.reason,\r\n\t\t\t\t\tuserActivity: findUserActivityByQuery(scheduledReport.query)\r\n\t\t\t\t}\r\n\r\n\t\t\t\terrorApi.sendBugReport(payload)\r\n\t\t\t\t.then(() => { this.remove(scheduledReport); })\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\r\nconst scheduledBugReports = new ScheduledBugReports();\r\nexport {scheduledBugReports}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\n\r\n// material-ui\r\nimport Button from \"@material-ui/core/Button\";\r\n\r\n// react\r\nimport { Popup } from \"components/layout/popup\";\r\n\r\n// redux (selectors)\r\nimport { isOfflineMode, getOfflineReason } from \"redux/reducers/app/selectors\";\r\nimport { getUserGuid } from \"redux/reducers/session/user/selectors\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getGuid as getExamGuid } from \"redux/reducers/exam/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { shouldNotifyImmediatelyOnLostConnection } from \"redux/reducers/settings/client/selectors\";\r\nimport {\r\n getAppData,\r\n getSettingsData,\r\n getExamData,\r\n getSessionData,\r\n} from \"redux/reducers/selectors\";\r\n\r\n// redux (action-types)\r\nimport { PAUSE_EXAM, RESUME_EXAM } from \"redux/reducers/exam/action-types\";\r\n\r\n// utils\r\nimport { scheduledBugReports } from \"libs/browser_storage/apis/scheduled-bug-reports\";\r\n\r\nconst setExamPaused = (value) => ({\r\n type: value ? PAUSE_EXAM : RESUME_EXAM,\r\n});\r\n\r\nconst POPUPS = {\r\n NONE: 0,\r\n CONNECTION_LOST: 1,\r\n CONNECTION_RETURNED: 2,\r\n};\r\n\r\nclass ConnectionLossNotifier extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = { popup: POPUPS.NONE };\r\n }\r\n\r\n componentDidUpdate(previousProps) {\r\n if (previousProps.offline !== this.props.offline) {\r\n if (this.props.offline) {\r\n this.handleOffline();\r\n } else {\r\n this.handleOnline();\r\n }\r\n }\r\n }\r\n\r\n handleOffline() {\r\n this.scheduleBugReport();\r\n if (this.props.notifyImmediately) {\r\n this.setState({ popup: POPUPS.CONNECTION_LOST });\r\n this.props.setExamPaused(true);\r\n }\r\n }\r\n\r\n handleOnline() {\r\n if (this.props.notifyImmediately) {\r\n this.setState({ popup: POPUPS.CONNECTION_RETURNED });\r\n this.props.setExamPaused(false);\r\n }\r\n }\r\n\r\n scheduleBugReport() {\r\n const { userGuid, examGuid, offlineReason } = this.props;\r\n\r\n const message = \"[Automated Report] Network error during exam\";\r\n const query = { userGuid: userGuid, examGuid: examGuid };\r\n\r\n scheduledBugReports.schedule(message, query, offlineReason);\r\n }\r\n\r\n render() {\r\n if (!this.props.notifyImmediately) {\r\n return null;\r\n }\r\n\r\n switch (this.state.popup) {\r\n case POPUPS.NONE:\r\n return null;\r\n case POPUPS.CONNECTION_LOST:\r\n return this.ConnectionLostPopup;\r\n case POPUPS.CONNECTION_RETURNED:\r\n return this.ConnectionReturnedPopup;\r\n default:\r\n throw \"Connection lost - invalid popup\";\r\n }\r\n }\r\n\r\n get ConnectionLostPopup() {\r\n const title = \"Connection lost!\";\r\n const text =\r\n \"We've noticed that your connection has gone down. Please speak \" +\r\n \"to your invigilator about this. Any unsaved answers will be \" +\r\n \"synced to XAMS as soon as the connection returns.\";\r\n\r\n return ;\r\n }\r\n\r\n get ConnectionReturnedPopup() {\r\n const title = \"Connection returned! :)\";\r\n const text =\r\n \"Your connection has returned and your answers were saved. \" +\r\n \"Click 'OK' to continue your exam\";\r\n\r\n const onClick = () => {\r\n this.setState({ popup: POPUPS.NONE });\r\n this.props.setExamPaused(false);\r\n };\r\n\r\n const okButton = {\"OK\"} ;\r\n\r\n return ;\r\n }\r\n}\r\n\r\nConnectionLossNotifier.propTypes = {\r\n offline: PropTypes.bool.isRequired,\r\n notifyImmediately: PropTypes.bool.isRequired,\r\n\r\n // temp\r\n examGuid: PropTypes.string.isRequired,\r\n userGuid: PropTypes.string.isRequired,\r\n};\r\n\r\nconst mapStoreToProps = (store) => {\r\n const appData = getAppData(store);\r\n\r\n return {\r\n offline: isOfflineMode(appData),\r\n offlineReason: getOfflineReason(appData),\r\n notifyImmediately: shouldNotifyImmediatelyOnLostConnection(\r\n getClientSettingsData(getSettingsData(store))\r\n ),\r\n\r\n // temp\r\n examGuid: getExamGuid(getExamData(store)),\r\n userGuid: getUserGuid(getUserSessionData(getSessionData(store))),\r\n };\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setExamPaused: (value) => dispatch(setExamPaused(value)),\r\n});\r\n\r\nConnectionLossNotifier = connect(\r\n mapStoreToProps,\r\n mapDispatchToProps\r\n)(ConnectionLossNotifier);\r\n\r\nexport { ConnectionLossNotifier };\r\n","\r\nimport {Machine} from 'xstate'\r\nimport {XStateConfig} from './xstate-config'\r\n\r\n\r\n// #########################################\r\n// STATE NAMES\r\n// #########################################\r\n\r\nconst STATES = {\r\n\tSTARTING_EXAM: 'starting_exam',\r\n\tRUNNING: 'exam_running',\r\n\tPAUSED: 'exam_paused',\r\n\tNETWORK_ERROR: 'network_error',\r\n\tCHECKING_SCHEDULE_COMPLETION: 'checking_if_schedule_completed',\r\n\tSCHEDULE_ALREADY_COMPLETED: 'schedule_already_complete',\t\r\n}\r\n\r\nconst RUNNING_STATES = {\r\n\tQUESTIONS: 'showing_questions',\r\n\tINSTRUCTIONS: 'showing_instructions'\r\n}\r\n\r\n\r\n// #########################################\r\n// EVENT NAMES\r\n// #########################################\r\n\r\nconst EVENTS = {\r\n\tERROR: 'error',\r\n\tPAUSE_EXAM: 'pause_exam',\r\n\tUNPAUSE_EXAM: 'unpause_exam',\r\n\tEXAM_STARTED: 'exam_started',\r\n\tSHOW_INSTRUCTIONS: 'show_exam_instructions',\r\n\tSHOW_QUESTIONS: 'show_exam_questions',\r\n\tSCHEDULE_INCOMPLETE: 'schedule_incomplete',\r\n\tSCHEDULE_COMPLETE: 'schedule_complete',\t\r\n}\r\n\r\n\r\n// #########################################\r\n// RUNNING_STATES\r\n// #########################################\r\n\r\nconst questions = new XStateConfig();\r\nquestions.addTransition(EVENTS.SHOW_INSTRUCTIONS, RUNNING_STATES.INSTRUCTIONS);\r\n\r\nconst instructions = new XStateConfig();\r\ninstructions.addTransition(EVENTS.SHOW_QUESTIONS, RUNNING_STATES.QUESTIONS);\r\n\r\n\r\n// #########################################\r\n// STATES\r\n// #########################################\r\n\r\nconst checkingScheduleCompletion = new XStateConfig();\r\ncheckingScheduleCompletion.addTransition(EVENTS.SCHEDULE_COMPLETE, STATES.SCHEDULE_ALREADY_COMPLETED);\r\ncheckingScheduleCompletion.addTransition(EVENTS.SCHEDULE_INCOMPLETE, STATES.STARTING_EXAM);\r\n\r\nconst scheduleAlreadyComplete = new XStateConfig();\r\n\r\nconst startingExam = new XStateConfig();\r\nstartingExam.addTransition(EVENTS.EXAM_STARTED, STATES.RUNNING);\r\nstartingExam.addTransition(EVENTS.ERROR, STATES.NETWORK_ERROR);\r\n\r\nconst running = new XStateConfig();\r\nrunning.initialState = RUNNING_STATES.QUESTIONS;\r\nrunning.addState(RUNNING_STATES.QUESTIONS, questions);\r\nrunning.addState(RUNNING_STATES.INSTRUCTIONS, instructions);\r\nrunning.addTransition(EVENTS.PAUSE_EXAM, STATES.PAUSED);\r\nrunning.addTransition(EVENTS.ERROR, STATES.NETWORK_ERROR);\r\n\r\nconst paused = new XStateConfig();\r\npaused.addTransition(EVENTS.UNPAUSE_EXAM, STATES.RUNNING);\r\npaused.addTransition(EVENTS.ERROR, STATES.NETWORK_ERROR);\r\n\r\nconst networkError = new XStateConfig();\r\n\r\n\r\n// #########################################\r\n// MACHINE\r\n// #########################################\r\n\r\nconst _examRunning = new XStateConfig();\r\n// _examRunning.initialState = STATES.STARTING_EXAM;\r\n_examRunning.initialState = STATES.CHECKING_SCHEDULE_COMPLETION;\r\n\r\n_examRunning.addState(STATES.CHECKING_SCHEDULE_COMPLETION, checkingScheduleCompletion);\r\n_examRunning.addState(STATES.SCHEDULE_ALREADY_COMPLETED, scheduleAlreadyComplete);\r\n_examRunning.addState(STATES.STARTING_EXAM, startingExam);\r\n_examRunning.addState(STATES.RUNNING, running);\r\n_examRunning.addState(STATES.PAUSED, paused);\r\n_examRunning.addState(STATES.NETWORK_ERROR, networkError);\r\n\r\nconst machine = Machine(_examRunning.toObject());\r\nmachine.id = \"Exam Running Machine\";\r\n\r\n\r\n// #########################################\r\n// EXPORT\r\n// #########################################\r\n\r\nconst examRunning = {\r\n\tmachine,\r\n\tEVENTS: {...EVENTS},\r\n\tSTATES: {...STATES},\r\n\tRUNNING_STATES: {...RUNNING_STATES}\r\n}\r\n\r\nexport {examRunning}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {ExamInstructionsPage} from './instructions'\r\nimport {ExamQuestionsPageRouter} from './questions'\r\nimport {ExamSaver} from 'components/pages/exam/exam_saver'\r\nimport {ConnectionLossNotifier} from './connection-loss-notifier'\r\n\r\n// machines\r\nimport {examRunning} from 'machines/exam-running'\r\n\r\n\r\nconst {EVENTS, RUNNING_STATES:STATES} = examRunning;\r\n\r\n\r\nconst PlayingExamView = () =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t\t \r\n\t\t\t\r\n\t\t\t {{\r\n\t\t\t \t\t[STATES.QUESTIONS]: () => (\r\n\t\t\t \t\t\t\r\n\t\t\t \t\t\t\t{(props) => }\r\n\t\t\t \t\t\t \r\n\t\t\t \t\t),\r\n\t\t\t \t\t[STATES.INSTRUCTIONS]: () => (\r\n\t\t\t \t\t\t\r\n\t\t\t \t\t\t\t{(props) => }\r\n\t\t\t \t\t\t \r\n\t\t\t \t\t)\r\n\t\t\t }}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {PlayingExamView}","\r\n// npm\r\nimport React from 'react'\r\n\r\nimport { check } from '@xams-utils/check-types'\r\nimport PropTypes from 'prop-types'\r\n\r\n// xams-components\r\nimport {StateMachine, StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {ExamStarter} from './exam-starter'\r\nimport {ExamPausedPage} from './pause/paused-page'\r\nimport {ExamPauseManager} from './pause/pause-manager'\r\nimport {PlayingExamView} from './playing_exam/view'\r\nimport {SetNetworkError} from 'components/pages/network_error/set-network-error'\r\nimport {ScheduleCompletionChecker} from 'components/pages/_exam/initializing/validating_schedule/completion-checker'\r\nimport {ScheduleCompletedPopup} from 'components/pages/_exam/initializing/validating_schedule/completed-popup'\r\n\r\n// machines\r\nimport {examRunning} from 'machines/exam-running'\r\n\r\n\r\nconst {STATES, EVENTS} = examRunning;\r\n\r\n\r\nconst ExamUnpauseControl = () => (\r\n\t\r\n\t\t{({onUnpause}) => }\r\n\t \r\n)\r\n\r\nconst ExamPauseControl = () => (\r\n\t\r\n\t\t{({onPause}) => }\r\n\t \r\n)\r\n\r\nconst getExamGuid = (_examGuid) => {\r\n if (check.object(_examGuid)) {\r\n const { examGuid } = _examGuid;\r\n if (check.string(examGuid)) {\r\n return examGuid;\r\n }\r\n } else if (check.string(_examGuid)) {\r\n return _examGuid;\r\n }\r\n\r\n debugger;\r\n};\r\n\r\nconst ExamRunningPageMachine = (_examGuid) =>\r\n{\r\n\tconst examGuid = getExamGuid(_examGuid);\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{{\r\n\t\t\t\t\t[STATES.CHECKING_SCHEDULE_COMPLETION]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.SCHEDULE_ALREADY_COMPLETED]: () => (\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\t\t\t\t\t\r\n\t\t\t\t\t[STATES.STARTING_EXAM]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.RUNNING]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t \r\n\t\t\t\t\t\t\t{PlayingExamView()}\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.PAUSED]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t \t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t \r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.NETWORK_ERROR]: () => (\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t)\r\n\t\t\t\t}}\r\n\t\t\t \r\n\t\t \r\n\t);\r\n}\r\n\r\n\r\nexport {ExamRunningPageMachine}","import React, { Component } from \"react\";\r\n\r\nimport Card from \"@material-ui/core/Card\";\r\nimport CardHeader from \"@material-ui/core/CardHeader\";\r\nimport CardContent from \"@material-ui/core/CardContent\";\r\nimport CardActions from \"@material-ui/core/CardActions\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport Button from \"@material-ui/core/Button\";\r\nimport Typography from \"@material-ui/core/Typography\";\r\nimport ErrorIcon from '@material-ui/icons/Error';\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nconst styles = ({ spacing, palette, breakpoints }) => ({\r\n root: {\r\n [breakpoints.down(800)]: {\r\n width: \"80%\",\r\n },\r\n [breakpoints.up(801)]: {\r\n width: 800,\r\n },\r\n marginLeft: 'auto', \r\n marginRight: 'auto',\r\n marginTop: spacing.unit * 2,\r\n },\r\n avatar: {\r\n backgroundColor: palette.error.dark \r\n },\r\n button: {\r\n marginLeft: 'auto', \r\n }\r\n});\r\n\r\nclass FullScreenError extends Component {\r\n state = {};\r\n render() {\r\n const { title, message, buttonClick, buttonText, classes } = this.props;\r\n const buttonProps = {\r\n color: \"primary\",\r\n variant: \"contained\",\r\n onClick: buttonClick,\r\n className: classes.button\r\n };\r\n\r\n return (\r\n \r\n ! }\r\n title={title}\r\n titleTypographyProps={{variant:\"h5\"}}\r\n />\r\n\r\n \r\n \r\n {message}\r\n \r\n \r\n \r\n {buttonText} \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nFullScreenError = withStyles(styles)(FullScreenError);\r\n\r\nexport { FullScreenError };\r\n","import React, { Component } from \"react\";\r\n\r\nimport { withStyles } from \"@material-ui/core/styles\";\r\nimport Button from \"@material-ui/core/Button\";\r\nimport Dialog from \"@material-ui/core/Dialog\";\r\nimport ListItemText from \"@material-ui/core/ListItemText\";\r\nimport ListItem from \"@material-ui/core/ListItem\";\r\nimport List from \"@material-ui/core/List\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\nimport AppBar from \"@material-ui/core/AppBar\";\r\nimport Toolbar from \"@material-ui/core/Toolbar\";\r\nimport IconButton from \"@material-ui/core/IconButton\";\r\nimport Typography from \"@material-ui/core/Typography\";\r\nimport CloseIcon from \"@material-ui/icons/Close\";\r\nimport Slide from \"@material-ui/core/Slide\";\r\nimport { Align } from \"components/layout/align\";\r\nimport { FullScreenError } from \"./error\";\r\n\r\nconst Transition = (props) =>{\r\n return ;\r\n }\r\n\r\nclass FullScreenDialog extends Component {\r\n state = {};\r\n\r\n render() {\r\n const { open, title, message, buttonClick, buttonText, classes } =\r\n this.props;\r\n\r\n const diagProps = {\r\n fullScreen: true,\r\n open,\r\n disableBackdropClick: true,\r\n TransitionComponent: Transition\r\n // style: {width: \"50vw\", height: \"50vh\"}\r\n };\r\n\r\n const contentProps = {\r\n style: { height: \"50vh\" },\r\n };\r\n\r\n const buttonProps = {\r\n color: \"primary\",\r\n variant: \"contained\",\r\n onClick: buttonClick,\r\n };\r\n\r\n const errorProps = { title, message, buttonClick, buttonText };\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {title}\r\n \r\n \r\n {buttonText}\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nconst styles = {\r\n appBar: {\r\n position: \"relative\",\r\n },\r\n flex: {\r\n flex: 1,\r\n },\r\n};\r\n\r\nFullScreenDialog = withStyles(styles)(FullScreenDialog);\r\n\r\nexport { FullScreenDialog };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport {\r\n SET_LEAVE_EXAM,\r\n SET_RESUME_EXAM,\r\n} from \"redux/reducers/exam/action-types\";\r\nimport { getSettingsData, getSessionData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getPreviewMode } from \"redux/reducers/settings/client/selectors\";\r\nimport { getClientSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getOrgId } from \"redux/reducers/session/client/selectors\";\r\nimport { getExamData } from \"redux/reducers/selectors\";\r\nimport { getShowExamInFullScreen } from \"redux/reducers/exam/selectors\";\r\n\r\nimport { openInFullScreen } from \"./full_screen/helper\";\r\nimport { FullScreenDialog } from \"./full_screen/full-screen-dialog\";\r\n\r\nimport {\r\n activityLogger,\r\n ACTIVITIES,\r\n} from \"libs/activity_logger/activity-logger\";\r\n\r\nimport { displayDateTime } from \"components/functional/timer-debug\";\r\n\r\nclass LeaveResumeExam extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleLeaveExam = this.handleLeaveExam.bind(this);\r\n this.handleResumeExam = this.handleResumeExam.bind(this);\r\n this.handleLeaveBrowser = this.handleLeaveBrowser.bind(this);\r\n this.handleRightClick = this.handleRightClick.bind(this);\r\n\r\n this.handleFullScreenChange = this.handleFullScreenChange.bind(this);\r\n this.getDialogProps = this.getDialogProps.bind(this);\r\n\r\n this.state = { fullScreen: true };\r\n }\r\n\r\n handleLeaveExam() {\r\n const { setLeaveExam, showDebug } = this.props;\r\n var currentDateTime = Date.now();\r\n if (showDebug) {\r\n console.log(\"Leave Exam\", displayDateTime(currentDateTime));\r\n }\r\n setLeaveExam(currentDateTime);\r\n }\r\n\r\n handleResumeExam() {\r\n const { setResumeExam, showDebug } = this.props;\r\n var currentDateTime = Date.now();\r\n if (showDebug) {\r\n console.log(\"Resume Exam\", displayDateTime(currentDateTime));\r\n }\r\n setResumeExam(currentDateTime);\r\n }\r\n\r\n handleRightClick(ev) {\r\n ev.preventDefault();\r\n }\r\n\r\n handleLeaveBrowser(ev) {\r\n ev.preventDefault();\r\n return (ev.returnValue = \"Are you sure you want to close?\");\r\n }\r\n\r\n componentDidMount() {\r\n const {\r\n detectLeaveExam,\r\n alertLeaveBrowser,\r\n preventRightClick,\r\n showExamInFullScreen,\r\n } = this.props;\r\n\r\n if (detectLeaveExam) {\r\n window.onfocus = () => {\r\n this.handleResumeExam();\r\n };\r\n window.onblur = () => {\r\n this.handleLeaveExam();\r\n };\r\n }\r\n\r\n if (alertLeaveBrowser) {\r\n window.addEventListener(\"beforeunload\", this.handleLeaveBrowser);\r\n }\r\n\r\n if (preventRightClick) {\r\n document.addEventListener(\"contextmenu\", this.handleRightClick);\r\n }\r\n\r\n if (showExamInFullScreen) {\r\n document.addEventListener(\r\n \"fullscreenchange\",\r\n this.handleFullScreenChange\r\n );\r\n }\r\n }\r\n\r\n componentWillUnmount() {\r\n const {\r\n detectLeaveExam,\r\n alertLeaveBrowser,\r\n preventRightClick,\r\n showExamInFullScreen,\r\n } = this.props;\r\n\r\n if (detectLeaveExam) {\r\n window.onfocus = null;\r\n window.onblur = null;\r\n }\r\n\r\n if (alertLeaveBrowser) {\r\n window.removeEventListener(\"beforeunload\", this.handleLeaveBrowser);\r\n }\r\n\r\n if (preventRightClick) {\r\n document.removeEventListener(\"contextmenu\", this.handleRightClick);\r\n }\r\n\r\n if (showExamInFullScreen) {\r\n document.removeEventListener(\r\n \"fullscreenchange\",\r\n this.handleFullScreenChange\r\n );\r\n }\r\n }\r\n\r\n handleFullScreenChange(event) {\r\n const fullScreen = document.fullscreenElement;\r\n activityLogger.log(ACTIVITIES.FULL_SCREEN, { fullScreen });\r\n this.setState({ fullScreen });\r\n }\r\n\r\n handleGoToFullScreen() {\r\n openInFullScreen();\r\n }\r\n\r\n getDialogProps() {\r\n const { showExamInFullScreen } = this.props;\r\n const { fullScreen, tabFocus } = this.state;\r\n\r\n let dialogProps = { open: false };\r\n\r\n if (showExamInFullScreen) {\r\n if (!fullScreen) {\r\n dialogProps = {\r\n open: true,\r\n title: \"Full Screen\",\r\n message: \"This exam must be taken in Full Screen\",\r\n buttonText: \"Return to Exam\",\r\n buttonClick: this.handleGoToFullScreen,\r\n };\r\n }\r\n }\r\n\r\n return dialogProps;\r\n }\r\n\r\n render() {\r\n const { children, showExamInFullScreen } = this.props;\r\n const dialogProps = this.getDialogProps();\r\n\r\n if (!showExamInFullScreen) return children;\r\n\r\n return (\r\n \r\n {children}\r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n const previewMode = getPreviewMode(clientSettingsData) === \"1\";\r\n\r\n const sessionData = getSessionData(store);\r\n const clientSessionData = getClientSessionData(sessionData);\r\n const orgId = getOrgId(clientSessionData);\r\n\r\n const alertLeaveBrowser = orgId === 29 && !previewMode;\r\n\r\n const examData = getExamData(store);\r\n const showExamInFullScreen = getShowExamInFullScreen(examData);\r\n\r\n return {\r\n detectLeaveExam: true,\r\n showDebug: false,\r\n alertLeaveBrowser,\r\n preventRightClick: true,\r\n showExamInFullScreen,\r\n };\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setLeaveExam: (value) => {\r\n dispatch({\r\n type: SET_LEAVE_EXAM,\r\n value,\r\n });\r\n },\r\n setResumeExam: (value) => {\r\n dispatch({\r\n type: SET_RESUME_EXAM,\r\n value,\r\n });\r\n },\r\n});\r\n\r\nLeaveResumeExam = connect(mapStoreToProps, mapDispatchToProps)(LeaveResumeExam);\r\n\r\nexport { LeaveResumeExam };\r\n","import { api } from \"../api\";\r\nimport { ENDPOINTS } from \"../constants\";\r\n\r\nconst subscribe = (query, groupID) => {\r\n console.log(ENDPOINTS.DASH.EVENTS);\r\n return api.subscribeSignalR(ENDPOINTS.DASH.EVENTS, query, groupID);\r\n};\r\n\r\nconst dashApi = {\r\n subscribe: (apiData) => {\r\n return subscribe(apiData);\r\n },\r\n};\r\n\r\nexport { dashApi };\r\n","// npm\r\nimport React from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\n// redux (selectors)\r\nimport { getExamData, getSessionData } from \"redux/reducers/selectors\";\r\nimport { getFormRunGuid } from \"redux/reducers/exam/selectors\";\r\nimport { getClientSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getOrgId } from \"redux/reducers/session/client/selectors\";\r\n\r\n// redux (action-types)\r\nimport {\r\n PAUSE_EXAM,\r\n RESUME_EXAM,\r\n CONFIRM_RESUME_EXAM,\r\n} from \"redux/reducers/exam/action-types\";\r\n\r\nimport { dashApi } from \"libs/api/interface/api-dash\";\r\n\r\n// utils\r\nimport { scheduledBugReports } from \"libs/browser_storage/apis/scheduled-bug-reports\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nconst setExamPaused = (value) => ({\r\n type: value ? PAUSE_EXAM : RESUME_EXAM,\r\n});\r\n\r\nconst setConfirmResumeExam = (value) => ({\r\n type: CONFIRM_RESUME_EXAM,\r\n value,\r\n});\r\n\r\nconst POPUPS = {\r\n NONE: 0,\r\n CONNECTION_LOST: 1,\r\n CONNECTION_RETURNED: 2,\r\n};\r\n\r\nclass ExamPauseNotifier extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.pauseExam = this.pauseExam.bind(this);\r\n this.unPauseExam = this.unPauseExam.bind(this);\r\n\r\n const { formRunGuid } = this.props;\r\n\r\n if (check.nonEmptyString(formRunGuid)) {\r\n this.formRunGuid = formRunGuid;\r\n } else {\r\n this.formRunGuid = null;\r\n }\r\n\r\n this.startHub = this.startHub.bind(this);\r\n\r\n this.hubConnection = null;\r\n }\r\n\r\n componentDidMount() {\r\n if (check.nonEmptyString(this.formRunGuid)) {\r\n const { canOrgPause } = this.props;\r\n\r\n if (canOrgPause) {\r\n this.startHub();\r\n }\r\n }\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n if (!check.nonEmptyString(this.formRunGuid)) {\r\n const { formRunGuid: prevFormGuid } = prevProps;\r\n const { formRunGuid, canOrgPause } = this.props;\r\n\r\n if (\r\n !check.nonEmptyString(prevFormGuid) &&\r\n check.nonEmptyString(formRunGuid)\r\n ) {\r\n this.formRunGuid = formRunGuid;\r\n\r\n if (canOrgPause) {\r\n console.log(\"ExamPauseNotifier - Update formRunGuid\", formRunGuid);\r\n \r\n this.startHub();\r\n }\r\n }\r\n }\r\n }\r\n\r\n startHub() {\r\n const { subscribe } = dashApi;\r\n const { formRunGuid } = this.props;\r\n\r\n console.log(\"ExamPauseNotifier - Starting HUB\", formRunGuid);\r\n\r\n this.hubConnection = subscribe({ formRunGuid });\r\n\r\n this.hubConnection.on(\"dashevent\", (msg) => {\r\n if (check.nonEmptyString(msg)) {\r\n console.log(\"ExamPauseNotifier - Received hub event: \" + msg);\r\n\r\n const data = JSON.parse(msg);\r\n\r\n if (check.nonEmptyObject(data)) {\r\n const { paused } = data;\r\n\r\n if (check.boolean(paused)) {\r\n if (paused) {\r\n this.pauseExam();\r\n } else {\r\n this.unPauseExam();\r\n }\r\n }\r\n }\r\n }\r\n });\r\n\r\n }\r\n\r\n componentWillUnmount() {\r\n if (this.hubConnection != null) {\r\n console.log(\"ExamPauseNotifier - Stopping signalR..\");\r\n this.hubConnection.stop();\r\n }\r\n }\r\n\r\n pauseExam() {\r\n const { setExamPaused } = this.props;\r\n\r\n setExamPaused(true);\r\n }\r\n\r\n unPauseExam() {\r\n const { setExamPaused } = this.props;\r\n\r\n setExamPaused(false);\r\n }\r\n\r\n render() {\r\n return null;\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const examData = getExamData(store);\r\n const formRunGuid = getFormRunGuid(examData);\r\n\r\n const sessionData = getSessionData(store);\r\n const clientSessionData = getClientSessionData(sessionData);\r\n const orgId = getOrgId(clientSessionData);\r\n const canOrgPause = orgId === 29;\r\n\r\n return { formRunGuid, orgId, canOrgPause, canOrgPause };\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setExamPaused: (value) => dispatch(setExamPaused(value)),\r\n confirmResumeExam: (value) => dispatch(setConfirmResumeExam(value)),\r\n});\r\n\r\nExamPauseNotifier = connect(\r\n mapStoreToProps,\r\n mapDispatchToProps\r\n)(ExamPauseNotifier);\r\n\r\nexport { ExamPauseNotifier };\r\n","// npm\r\nimport React from \"react\";\r\n\r\n// react\r\nimport { ExamRunningPageMachine } from \"./machine\";\r\nimport { LeaveResumeExam } from \"./leave_resume_exam/leave-resume-exam\";\r\nimport { ExamPauseNotifier } from './pause/exam-pause-notifier'\r\n\r\n\r\nconst ExamRunningPage = ({ match }) => {\r\n const examGuid = match.params.examGuid;\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n );\r\n};\r\n\r\nexport { ExamRunningPage };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {Redirect} from 'react-router-dom'\r\n\r\n\r\nconst RedirectToExamIntro = (props) =>\r\n{\r\n\tconst url = `/exam/${props.match.params.examGuid}/intro`;\r\n\treturn ;\r\n}\r\n\r\nRedirectToExamIntro.propTypes = {\r\n\tmatch: PropTypes.shape({\r\n\t\tparams: PropTypes.shape({\r\n\t\t\texamGuid: PropTypes.string.isRequired\r\n\t\t}).isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nexport {RedirectToExamIntro}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {Redirect} from 'react-router-dom'\r\nimport {Switch, Route} from 'react-router-dom'\r\n\r\n// react\r\nimport {ExamIntro} from './intro'\r\nimport {ExamOutro} from './outro'\r\nimport {ExamRunningPage} from './running/page'\r\nimport {RedirectToExamIntro} from './redirect-to-intro'\r\n\r\n\r\nclass ExamRouter extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.state = {wasInIntro: false};\r\n\t}\r\n\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tthis.setState({wasInIntro: true});\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\t// ExamIntroPage sets up current question id (as part of the 'attempt resume' process). \r\n\t\t// I've enforced this router to always go through the 'intro' screen first so as to ensure\r\n\t\t// the exam gets set up correctly (there were issues before when reloading page).\r\n\t\t// This process may need a re-think in the future, but for now it's working.\r\n\t\tif (!this.state.wasInIntro) {\r\n\t\t\treturn ;\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n}\r\n\r\nExamRouter.propTypes = {\r\n\tlocation: PropTypes.object.isRequired\r\n}\r\n\r\n\r\nexport {ExamRouter}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport {withRouter} from 'react-router-dom'\r\n\r\n// react\r\nimport {ExamRouter} from './router'\r\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\r\n\r\n// redux (selectors)\r\nimport {getExamData} from 'redux/reducers/selectors'\r\nimport {getName} from 'redux/reducers/exam/selectors'\r\n\r\n\r\n// ExamInitializedPage (not connected to store)\r\n// --------------------------------------------------------\r\n\r\nlet ExamInitializedPage = ({examName, location}) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t\t\r\n\t\t \r\n\t)\r\n}\r\n\r\nExamInitializedPage.propTypes = {\r\n\texamName: PropTypes.string.isRequired\r\n}\r\n\r\n\r\n// ExamInitializedPage (connected to store)\r\n// --------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\texamName: getName(getExamData(store))\r\n});\r\n\r\nExamInitializedPage = connect(mapStoreToProps)(ExamInitializedPage);\r\nExamInitializedPage = withRouter(ExamInitializedPage);\r\n\r\n\r\n// Export\r\n// --------------------------------------------------------\r\nexport {ExamInitializedPage}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateMachine, StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {ExamClearer} from './exam-clearer'\r\nimport {InitializingView} from './initializing/view'\r\nimport {ExamInitializedPage} from './initialized/page'\r\nimport {SetNetworkError} from 'components/pages/network_error/set-network-error'\r\n\r\n// machines\r\nimport {examInit} from 'machines/exam-init'\r\n\r\n\r\nconst {EVENTS, STATES} = examInit;\r\n\r\n\r\nconst ExamInitializationMachine = ({examGuid}) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{{\r\n\t\t\t\t\t[STATES.CLEARING_PREVIOUS_EXAM]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{({onClear}) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.INITIALIZING]: () => (\r\n\t\t\t\t\t\tInitializingView(examGuid)\r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.NETWORK_ERROR]: () => (\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.INITIALIZED]: () => (\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t)\r\n\t\t\t\t}}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {ExamInitializationMachine}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {ExamInitializationMachine} from './machine'\r\n\r\n\r\nconst ExamPage = ({match}) => {\r\n\treturn ;\r\n}\r\n\r\nExamPage.propTypes = {\r\n\tmatch: PropTypes.shape({\r\n\t\tparams: PropTypes.shape({\r\n\t\t\texamGuid: PropTypes.string.isRequired\r\n\t\t}).isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\nexport {ExamPage}","import {api} from '../api'\nimport {ENDPOINTS} from '../constants'\n\n\nconst getCourseAttempts=(payload)=>{\n return api.post(ENDPOINTS.ELEARNING.ATTEMPTS.GET, payload);\n}\n\nconst getCourseAttemptDetails=(formRunGuid)=>{\n const payload = {\n\t\tparameters:[\n\t\t\t{\n\t\t\t\tname: \"GUID\",\n\t\t\t\tvalue: formRunGuid,\n\t\t\t\texpression: \"equals\"\n\t\t\t}\n ],\n properties: [\n \"Course.ID\",\n \"Course.GUID\",\n \"Course.Name\",\n \"ID\",\n \"GUID\",\n \"Status\",\n \"DateStarted\",\n \"DateEnded\",\n \"LearningObjectID\",\n\t\t \"LearningObject.TextMessageID\",\n \"ScoAttempts\"\n ],\n collections: [\n {\n name: \"ScoAttempts\",\n properties: [\n \"Sco.ID\",\n \"Sco.GUID\",\n \"Sco.Name\",\n \"Sco.Url\",\n \"ID\",\n \"GUID\",\n \"Status\",\n \"DateStarted\",\n \"DateEnded\"\n ]\n }\n ],\n \"resolvers\": [\n {\n \"service\": \"/xams.Repo.Service.App/Service\",\n \"api\": \"/api/query\",\n \"entity\": \"Objects\",\n \"property\": \"LearningObject\",\n \"parameters\": [\n {\n \"name\": \"ID\",\n \"value\": \"LearningObjectID\",\n \"expression\": \"equals\"\n }\n ],\n \"properties\": [\n \"ID\",\n \"TextMessageID\"\n ]\n }\n ] \n\t}\n\n\treturn api.post(ENDPOINTS.ELEARNING.COURSE.QUERY, payload); \n}\n\nconst getCourseAttemptDetailsDev=(formRunGuid)=>{\n const payload = {\n\t\tparameters:[\n\t\t\t{\n\t\t\t\tname: \"GUID\",\n\t\t\t\tvalue: formRunGuid,\n\t\t\t\texpression: \"equals\"\n\t\t\t}\n ],\n properties: [\n \"Course.ID\",\n \"Course.GUID\",\n \"Course.Name\",\n \"ID\",\n \"GUID\",\n \"Status\",\n \"DateStarted\",\n \"DateEnded\",\n \"ScoAttempts\"\n ],\n collections: [\n {\n name: \"ScoAttempts\",\n properties: [\n \"Sco.ID\",\n \"Sco.GUID\",\n \"Sco.Name\",\n \"Sco.Url\",\n \"ID\",\n \"GUID\",\n \"Status\",\n \"DateStarted\",\n \"DateEnded\"\n ]\n }\n ] \n\t}\n\n\treturn api.post(ENDPOINTS.ELEARNING.COURSE.QUERY, payload); \n}\n\nconst getCourseInfo=(payload)=>{\n return api.post(ENDPOINTS.ELEARNING.COURSE.INFO, payload); \n}\n\nconst getCourseDetails=(payload)=>{\n return api.post(ENDPOINTS.ELEARNING.COURSE.GET, payload);\n}\n\nconst getScormData=(scoAttemptId)=>{\n const endPoint = `${ENDPOINTS.ELEARNING.SCORM.GET}/${scoAttemptId}`;\n return api.get(endPoint, {});\n}\n\nconst saveScormData=(scoAttemptId, payload)=>{\n const endPoint = `${ENDPOINTS.ELEARNING.SCORM.SAVE}/${scoAttemptId}`;\n return api.post(endPoint, payload);\n}\n\nconst resetScormData=(payload)=>{\n return api.post(ENDPOINTS.ELEARNING.SCORM.RESET, payload);\n}\n\nconst eLearningApi = {\n getCourseAttempts,\n getCourseAttemptDetails,\n getCourseAttemptDetailsDev,\n getScormData,\n saveScormData,\n resetScormData,\n getCourseDetails,\n getCourseInfo\n};\n\nexport { eLearningApi };\n","import React from \"react\";\r\nimport moment from \"moment\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport Hidden from \"@material-ui/core/Hidden\";\r\n\r\nimport StarBorderIcon from \"@material-ui/icons/StarBorder\";\r\nimport StarHalfIcon from \"@material-ui/icons/StarHalf\";\r\nimport StarIcon from \"@material-ui/icons/Star\";\r\nimport BlockIcon from \"@material-ui/icons/Block\";\r\nimport ClearIcon from \"@material-ui/icons/Clear\";\r\n\r\n\r\nconst hasScormFailed = (status) => status === 3;\r\nconst hasScormCompleted = (status) => status === 2;\r\nconst hasScormStarted = (status) => status === 1;\r\nconst hasScormNotStarted = (status) => status === 0;\r\n\r\nconst displayDate = (_date, long = true) => {\r\n const date = check.integer(_date)\r\n ? new moment(_date * 1000)\r\n : new moment(_date);\r\n\r\n const text = long\r\n ? date.format(\"dddd, MMMM Do YYYY, HH:mm\")\r\n : date.format(\"DD/MM/YY, HH:mm\");\r\n\r\n return text;\r\n};\r\n\r\nconst getStatusIcon = (status, _enabled = true) => {\r\n const enabled = hasScormFailed(status) ? true : _enabled;\r\n const iconProps = { color: enabled ? \"primary\" : \"disabled\" };\r\n\r\n if (hasScormCompleted(status)) return ;\r\n else if (hasScormStarted(status)) return ;\r\n else if (hasScormFailed(status)) return ;\r\n\r\n return enabled ? (\r\n \r\n ) : (\r\n \r\n );\r\n};\r\n\r\nconst getStatusText = (status, dateStarted, dateEnded) => {\r\n if (hasScormCompleted(status)) {\r\n return (\r\n \r\n {\"Completed \"}\r\n {displayDate(dateEnded, false)} \r\n {displayDate(dateEnded, true)} \r\n \r\n );\r\n } else if (hasScormStarted(status)) {\r\n return (\r\n \r\n {\"Started \"}\r\n {displayDate(dateStarted, false)} \r\n {displayDate(dateStarted, true)} \r\n \r\n );\r\n }\r\n else if (hasScormFailed(status)) {\r\n return 'Failed'\r\n }\r\n\r\n return \"Not started\";\r\n};\r\n\r\nconst getStatusButtonText = (status) => {\r\n if (hasScormCompleted(status)) return \"Review\";\r\n else if (hasScormStarted(status)) return \"Resume\";\r\n\r\n return \"Start\";\r\n};\r\n\r\nconst checkForCompletedSwitch = (eLearning, currentValue=false) => {\r\n let allCompleted = true;\r\n let oneCompleted = false;\r\n\r\n eLearning.forEach((eLearning) => {\r\n const { status } = eLearning;\r\n\r\n if (hasScormCompleted(status)) oneCompleted = true;\r\n else allCompleted = false;\r\n });\r\n\r\n let hasCompletedSwitch = false;\r\n let completedSwitch = false;\r\n\r\n if (!oneCompleted) {\r\n hasCompletedSwitch = false;\r\n completedSwitch = false;\r\n } else if (allCompleted) {\r\n hasCompletedSwitch = false;\r\n completedSwitch = true;\r\n } else {\r\n hasCompletedSwitch = true;\r\n completedSwitch = currentValue;\r\n }\r\n\r\n return { hasCompletedSwitch, completedSwitch };\r\n};\r\n\r\nconst getNotCompleted = (eLearning) => {\r\n return eLearning.filter((eLearning) => {\r\n const { status } = eLearning;\r\n return !hasScormCompleted(status);\r\n });\r\n};\r\n\r\nconst getCourseData = (parsedReponse) => {\r\n if (!parsedReponse) return null;\r\n\r\n const { results } = parsedReponse;\r\n if (check.nonEmptyArray(results)) {\r\n\r\n return convertCourseData(results[0]);\r\n }\r\n\r\n return [];\r\n};\r\n\r\nconst convertCourseData=(data)=>{\r\n if (!check.nonEmptyObject(data)) return null;\r\n\r\n const _data = {\r\n id: data.Course_ID || data.courseID,\r\n attemptId: data.ID || data.id,\r\n name: data.Course_Name || data.courseName,\r\n status: data.Status || data.status,\r\n dateStarted: data.DateStarted || data.dateStarted,\r\n dateEnded: data.DateEnded || data.dateEnded,\r\n guid: data.GUID || data.guid,\r\n courseInfoTextId: data.LearningObject_TextMessageID,\r\n scos: convertCourseDataScos(data.ScoAttempts || data.scoAttemptDetails)\r\n };\r\n\r\n return _data;\r\n}\r\n\r\nconst convertCourseDataScos=(scos)=>{\r\n if (!scos) return [];\r\n\r\n return scos.map((sco, index) => {\r\n return {\r\n id: sco.Sco_ID || sco.scoID,\r\n attemptId: sco.ID || sco.id,\r\n name: sco.Sco_Name || sco.scoName,\r\n href: sco.Sco_Url || sco.url,\r\n status: sco.Status || sco.status,\r\n //status: index===0? 3 : sco.Status || sco.status,\r\n dateStarted: sco.DateStarted || sco.dateStarted,\r\n dateEnded: sco.DateEnded || sco.dateEnded,\r\n guid: sco.GUID || sco.guid,\r\n };\r\n }); \r\n}\r\n\r\nconst getInitialScormData = (parsedResponse, studentInfo) => {\r\n const { attemptData, courseAttemptDetails } = parsedResponse;\r\n const initialData = attemptData\r\n ? attemptData.data\r\n ? [...attemptData.data]\r\n : []\r\n : [];\r\n\r\n const coreEntry = initialData.length === 0 ? \"ab-initio\" : \"resume\";\r\n initialData.push({ e: \"cmi.core.entry\", v: coreEntry });\r\n\r\n const {name, id} = studentInfo;\r\n initialData.push({ e: \"cmi.core.student.id\", v: id });\r\n initialData.push({ e: \"cmi.core.student.name\", v: name });\r\n\r\n const courseData = convertCourseData(courseAttemptDetails);\r\n\r\n return { initialData, courseData};\r\n};\r\n\r\nconst getCourseDataFromSaveResponse=(parsedResponse)=>{\r\n const {courseData} = parsedResponse;\r\n const _courseData = courseData ? courseData : parsedResponse;\r\n\r\n return convertCourseData(_courseData);\r\n}\r\n\r\nexport {\r\n getStatusIcon,\r\n getStatusText,\r\n getStatusButtonText,\r\n hasScormCompleted,\r\n hasScormStarted,\r\n hasScormFailed,\r\n hasScormNotStarted,\r\n checkForCompletedSwitch,\r\n getNotCompleted,\r\n getCourseData,\r\n getInitialScormData,\r\n getCourseDataFromSaveResponse\r\n};\r\n","const CONSTANTS = {\r\n COURSE: \"course\",\r\n SCO: \"sco\",\r\n};\r\n\r\nexport {CONSTANTS}","import React from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { isResetScheduleCheatActive } from \"redux/reducers/settings/app/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\n\r\nimport DeleteIcon from \"@material-ui/icons/Delete\";\r\nimport IconButton from \"@material-ui/core/IconButton\";\r\nimport Hidden from \"@material-ui/core/Hidden\";\r\n\r\nlet ResetScormButton = (props) => {\r\n const { useResetScheduleCheat, onClick } = props;\r\n\r\n if (true || !useResetScheduleCheat) return null;\r\n\r\n const buttonProps = {\r\n edge: \"end\",\r\n ariaLabel: \"reset\",\r\n onClick,\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n );\r\n};\r\n\r\nconst mapStoreToProps = (store) => ({\r\n useResetScheduleCheat: isResetScheduleCheatActive(\r\n getAppSettingsData(getSettingsData(store))\r\n ),\r\n});\r\n\r\nResetScormButton = connect(mapStoreToProps)(ResetScormButton);\r\n\r\nexport { ResetScormButton };\r\n","import React from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { withMessages } from \"components/hocs/messages\";\r\n\r\nimport { isResetScheduleCheatActive } from \"redux/reducers/settings/app/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\n\r\nimport Button from \"@material-ui/core/Button\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport ArrowForwardIcon from \"@material-ui/icons/ArrowForward\";\r\nimport ArrowBackIcon from \"@material-ui/icons/ArrowBack\";\r\nimport Hidden from \"@material-ui/core/Hidden\";\r\n\r\n\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nconst schedulesMessageId = MESSAGE_IDS.SCHEDULE.APP_BAR_TITLE;\r\n\r\nconst styles = ({ spacing, palette, breakpoints }) => ({\r\n button: {\r\n [breakpoints.up('sm')]: {\r\n width: 120,\r\n },\r\n [breakpoints.down('xs')]: {\r\n paddingRight: 0,\r\n minWidth: \"0px !important\",\r\n }, \r\n backgroundColor: palette.secondary.main + \"!important\",\r\n color: palette.secondary.contrastText + \"!important\",\r\n },\r\n leftIcon: {\r\n marginRight: spacing.unit,\r\n },\r\n});\r\n\r\nlet GoSchedulesButton = (props) => {\r\n const { classes, useResetScheduleCheat, onClick, messages } = props;\r\n const title = messages[schedulesMessageId];\r\n const buttonProps = {\r\n variant: \"contained\",\r\n color: useResetScheduleCheat ? \"primary\" : null,\r\n size: \"small\",\r\n className: classes.button,\r\n onClick,\r\n };\r\n\r\n return (\r\n \r\n {!useResetScheduleCheat && (\r\n \r\n )}\r\n {title} \r\n \r\n );\r\n};\r\n\r\nconst mapStoreToProps = (store) => ({\r\n useResetScheduleCheat: isResetScheduleCheatActive(\r\n getAppSettingsData(getSettingsData(store))\r\n ),\r\n});\r\n\r\nGoSchedulesButton = connect(mapStoreToProps)(GoSchedulesButton);\r\n\r\nGoSchedulesButton = withStyles(styles)(GoSchedulesButton);\r\n\r\nGoSchedulesButton = withMessages(GoSchedulesButton);\r\n\r\nexport { GoSchedulesButton };\r\n","import React, { Component } from \"react\";\r\n\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { eLearningApi } from \"libs/api/interface/api-e-learning\";\r\n\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport List from \"@material-ui/core/List\";\r\nimport ListItem from \"@material-ui/core/ListItem\";\r\nimport ListItemText from \"@material-ui/core/ListItemText\";\r\nimport ListItemAvatar from \"@material-ui/core/ListItemAvatar\";\r\nimport ListItemSecondaryAction from \"@material-ui/core/ListItemSecondaryAction\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport Hidden from \"@material-ui/core/Hidden\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\nimport CircularProgress from '@material-ui/core/CircularProgress';\r\n\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport { getStatusText, getStatusIcon } from \"../helper\";\r\nimport { CONSTANTS } from \"./constants\";\r\nimport { ResetScormButton } from \"./reset-scorm-button\";\r\nimport { GoSchedulesButton } from \"./go-schedules-button\";\r\n\r\nconst styles = ({ palette, spacing, breakpoints }) => ({\r\n root: {\r\n width: \"100%\",\r\n // maxWidth: 360,\r\n backgroundColor: palette.background.paper,\r\n },\r\n paper: {\r\n marginBottom: spacing.unit * 2,\r\n },\r\n leftIcon: {\r\n marginRight: spacing.unit,\r\n },\r\n buttons: {\r\n display: \"flex\",\r\n },\r\n secondaryAction: {\r\n paddingRight: spacing.unit * 2,\r\n alignItems: \"flex-start\",\r\n },\r\n listItem: {\r\n [breakpoints.down(\"xs\")]: {\r\n width: \"calc(100% - 48px)\",\r\n },\r\n },\r\n info: {\r\n padding: spacing.unit * 2,\r\n },\r\n});\r\n\r\nclass CourseTitle extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleReturnSchedules = this.handleReturnSchedules.bind(this);\r\n this.handleResetCourse = this.handleResetCourse.bind(this);\r\n this.handleResetSuccess = this.handleResetSuccess.bind(this);\r\n }\r\n\r\n get SecondaryAction() {\r\n const { page, classes, buttons, message } = this.props;\r\n\r\n if (buttons && !message) {\r\n return (\r\n \r\n {buttons.map(button=>button)}\r\n \r\n ); \r\n }\r\n\r\n if (page !== CONSTANTS.COURSE) return null;\r\n\r\n return (\r\n \r\n {this.ReturnButton}\r\n {this.ResetButton}\r\n \r\n );\r\n }\r\n\r\n get ResetButton() {\r\n const buttonProps = {\r\n onClick: this.handleResetCourse,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n get ReturnButton() {\r\n const buttonProps = {\r\n onClick: this.handleReturnSchedules,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n handleResetCourse() {\r\n const { courseAttemptId } = this.props;\r\n const payload = { courseAttemptId };\r\n\r\n eLearningApi\r\n .resetScormData(payload)\r\n .then(this.handleResetSuccess, this.handleResetFailue);\r\n }\r\n\r\n handleResetSuccess(response) {\r\n const data = JSON.parse(response);\r\n\r\n if (data) {\r\n const { courseData } = data;\r\n if (courseData) {\r\n const { updateCourseData } = this.props;\r\n updateCourseData(courseData);\r\n }\r\n }\r\n }\r\n\r\n handleResetFailure(response) {\r\n debugger;\r\n }\r\n\r\n handleReturnSchedules() {\r\n const { onReturnToSchedules } = this.props;\r\n onReturnToSchedules();\r\n }\r\n\r\n get Icon() {\r\n const { status, message } = this.props;\r\n if (message) return \r\n return getStatusIcon(status);\r\n }\r\n\r\n get PrimaryText() {\r\n const { name, courseName } = this.props;\r\n if (!check.assigned(courseName)) return name;\r\n\r\n return (\r\n \r\n {courseName && (\r\n
\r\n {courseName} \r\n
\r\n )}\r\n
{name}
\r\n
\r\n );\r\n }\r\n\r\n get SecondaryText() {\r\n const { status, dateStarted, dateEnded, message } = this.props;\r\n const statusText = getStatusText(status, dateStarted, dateEnded);\r\n\r\n return message?message:statusText;\r\n }\r\n\r\n get Info() {\r\n const { info, classes } = this.props;\r\n if (!info) return null;\r\n \r\n return (\r\n \r\n \r\n \r\n {info} \r\n
\r\n \r\n );\r\n }\r\n\r\n render() {\r\n const { classes, page } = this.props;\r\n const listItemProps = { alignItems: \"flex-start\" };\r\n\r\n if (page === CONSTANTS.COURSE)\r\n listItemProps.className = classes.listItem;\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n {this.Icon} \r\n \r\n \r\n {this.SecondaryAction}\r\n \r\n
\r\n {this.Info}\r\n \r\n );\r\n }\r\n}\r\n\r\nCourseTitle = withStyles(styles)(CourseTitle);\r\n\r\nexport { CourseTitle };\r\n","import React from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { isResetScheduleCheatActive } from \"redux/reducers/settings/app/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\n\r\nimport Button from \"@material-ui/core/Button\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport ArrowForwardIcon from \"@material-ui/icons/ArrowForward\";\r\nimport Hidden from \"@material-ui/core/Hidden\";\r\n\r\nimport { getStatusButtonText } from \"../helper\";\r\n\r\nconst styles = ({ spacing, palette, breakpoints }) => ({\r\n button: {\r\n [breakpoints.up('sm')]: {\r\n width: 120,\r\n },\r\n [breakpoints.down('xs')]: {\r\n paddingLeft: 0,\r\n minWidth: \"0px !important\",\r\n }, \r\n backgroundColor: palette.secondary.main + \"!important\",\r\n color: palette.secondary.contrastText + \"!important\",\r\n },\r\n rightIcon: {\r\n marginLeft: spacing.unit,\r\n },\r\n});\r\n\r\nlet PlayScormButton = (props) => {\r\n const { classes, status, useResetScheduleCheat, onClick } = props;\r\n\r\n const buttonProps = {\r\n variant: \"contained\",\r\n color: useResetScheduleCheat ? \"primary\" : null,\r\n size: \"small\",\r\n className: !useResetScheduleCheat ? classes.button : null,\r\n onClick,\r\n };\r\n\r\n return (\r\n \r\n {getStatusButtonText(status)} \r\n {!useResetScheduleCheat && (\r\n \r\n )}\r\n \r\n );\r\n};\r\n\r\nconst mapStoreToProps = (store) => ({\r\n useResetScheduleCheat: isResetScheduleCheatActive(\r\n getAppSettingsData(getSettingsData(store))\r\n ),\r\n});\r\n\r\nPlayScormButton = connect(mapStoreToProps)(PlayScormButton);\r\n\r\nPlayScormButton = withStyles(styles)(PlayScormButton);\r\n\r\nexport { PlayScormButton };\r\n","import React, { Component } from \"react\";\r\n\r\nimport { eLearningApi } from \"libs/api/interface/api-e-learning\";\r\n\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport List from \"@material-ui/core/List\";\r\nimport ListItem from \"@material-ui/core/ListItem\";\r\nimport ListItemText from \"@material-ui/core/ListItemText\";\r\nimport ListItemAvatar from \"@material-ui/core/ListItemAvatar\";\r\nimport ListItemSecondaryAction from \"@material-ui/core/ListItemSecondaryAction\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\n\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nimport { getStatusText, getStatusIcon, hasScormNotStarted, hasScormCompleted, hasScormFailed } from \"../helper\";\r\nimport { ResetScormButton } from \"./reset-scorm-button\";\r\nimport { PlayScormButton } from \"./play-scorm-button\";\r\n\r\nconst styles = ({ palette, spacing, breakpoints }) => ({\r\n sco: {\r\n width: \"100%\",\r\n // maxWidth: 360,\r\n backgroundColor: palette.background.paper,\r\n cursor: \"pointer\",\r\n },\r\n secondaryAction: {\r\n paddingRight: spacing.unit * 2,\r\n },\r\n listItem: {\r\n [breakpoints.down('xs')]: {\r\n width: 'calc(100% - 48px)',\r\n alignItems: 'flexStart'\r\n }, \r\n },\r\n paper: {\r\n [breakpoints.down('xs')]: {\r\n paddingLeft: 0,\r\n }, \r\n },\r\n listItemAvatar: {\r\n root: {\r\n alignItems: 'flexStart'\r\n }\r\n }\r\n});\r\n\r\nconst isScoEnabled=(scos,sco)=>{\r\n let scoFound = false;\r\n let enabled = true;\r\n\r\n if (hasScormFailed(sco.status)) return false;\r\n \r\n scos.forEach(_sco=>{\r\n if (!scoFound){\r\n if (sco.attemptId===_sco.attemptId){\r\n scoFound = true;\r\n }\r\n else{\r\n const {status} = _sco;\r\n if (!hasScormCompleted(status)) {\r\n enabled=false;\r\n }\r\n }\r\n }\r\n });\r\n\r\n return enabled;\r\n}\r\n\r\nlet CourseScos = (props) => {\r\n const { scos, classes, onPlaySco, updateCourseData, showCompletedMessage } = props;\r\n const lastSco = scos.length - 1;\r\n\r\n if (showCompletedMessage) return null;\r\n\r\n return (\r\n \r\n \r\n {scos.map((sco, index) => {\r\n const enabled = isScoEnabled(scos, sco);\r\n const scoProps = {\r\n key: index,\r\n enabled,\r\n index,\r\n onPlaySco,\r\n updateCourseData,\r\n isLast: index === lastSco,\r\n ...sco,\r\n };\r\n\r\n return ;\r\n })}\r\n
\r\n \r\n );\r\n};\r\n\r\nclass CourseSco extends Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handlePlaySco = this.handlePlaySco.bind(this);\r\n this.handleResetSco = this.handleResetSco.bind(this);\r\n this.handleResetSuccess = this.handleResetSuccess.bind(this);\r\n }\r\n\r\n handlePlaySco() {\r\n const { index, onPlaySco } = this.props;\r\n\r\n onPlaySco(index);\r\n }\r\n\r\n handleResetSco() {\r\n const { attemptId } = this.props;\r\n const payload = { scoAttemptId: attemptId };\r\n\r\n eLearningApi\r\n .resetScormData(payload)\r\n .then(this.handleResetSuccess, this.handleResetFailue);\r\n }\r\n\r\n handleResetSuccess(response) {\r\n const data = JSON.parse(response);\r\n\r\n if (data) {\r\n const { courseData } = data;\r\n if (courseData) {\r\n const { updateCourseData } = this.props;\r\n updateCourseData(courseData);\r\n }\r\n }\r\n }\r\n\r\n handleResetFailure(response) {\r\n debugger;\r\n }\r\n\r\n get SecondaryAction() {\r\n const { classes, enabled, status } = this.props;\r\n if (!enabled || hasScormFailed(status)) return null;\r\n return (\r\n \r\n {this.resetButton}\r\n {this.scormButton}\r\n \r\n );\r\n }\r\n\r\n get resetButton() {\r\n const { status } = this.props;\r\n\r\n return hasScormNotStarted(status) ? null : (\r\n \r\n );\r\n }\r\n\r\n get scormButton() {\r\n const { status } = this.props;\r\n const buttonProps = { status, onClick: this.handlePlaySco, status };\r\n return ;\r\n }\r\n\r\n get Icon() {\r\n const { status, enabled } = this.props;\r\n\r\n return getStatusIcon(status, enabled);\r\n }\r\n\r\n get PrimaryText() {\r\n const { name } = this.props;\r\n\r\n return name;\r\n }\r\n\r\n get SecondaryText() {\r\n const { status, dateStarted, dateEnded } = this.props;\r\n\r\n return getStatusText(status, dateStarted, dateEnded);\r\n }\r\n\r\n render() {\r\n const { isLast, classes, enabled } = this.props;\r\n\r\n const itemProps={\r\n alignItems: \"flex-start\",\r\n disabled: !enabled,\r\n className: classes.listItem,\r\n onClick:this.handlePlaySco\r\n }\r\n\r\n return (\r\n \r\n \r\n \r\n {this.Icon} \r\n \r\n \r\n {this.SecondaryAction}\r\n \r\n {!isLast && }\r\n \r\n );\r\n }\r\n}\r\n\r\nCourseScos = withStyles(styles)(CourseScos);\r\nCourseSco = withStyles(styles)(CourseSco);\r\n\r\nexport { CourseScos };\r\n","class SaveCounter{\r\n constructor(){\r\n this.counter = 0;\r\n this.counters = [];\r\n }\r\n\r\n getCounter(){\r\n const newCounter = parseInt(this.counter);\r\n this.counters = this.counters.concat([newCounter]);\r\n this.counter++;\r\n\r\n return newCounter;\r\n }\r\n\r\n removeCounter(_counter){\r\n this.counters = this.counters.filter(counter=>counter!==_counter);\r\n\r\n }\r\n\r\n isEmpty(){\r\n return this.counters.length===0;\r\n }\r\n}\r\n\r\nexport {SaveCounter}","import { Console, LOG_TYPES } from 'custom/console';\r\n\r\nconst openFullScreen = (elem) => {\r\n if (elem.requestFullscreen) {\r\n elem.requestFullscreen();\r\n } else if (elem.webkitRequestFullscreen) {\r\n /* Safari */\r\n elem.webkitRequestFullscreen();\r\n } else if (elem.msRequestFullscreen) {\r\n /* IE11 */\r\n elem.msRequestFullscreen();\r\n }\r\n};\r\n\r\nconst closeFullScreen = (elem) => {\r\n if (document.exitFullscreen) {\r\n if (document.fullscreenElement !== null) {\r\n document\r\n .exitFullscreen()\r\n .then(() => {\r\n Console.log(\"Closed iFrame\", LOG_TYPES.TEMP);\r\n })\r\n .catch((e) => {\r\n Console.log(e, LOG_TYPES.TEMP);\r\n });\r\n }\r\n } else if (document.webkitExitFullscreen) {\r\n /* Safari */\r\n if (document.webkitFullscreenElement !== null) {\r\n document.webkitExitFullscreen();\r\n }\r\n } else if (document.msExitFullscreen) {\r\n /* IE11 */\r\n if (document.msFullscreenElement !== null) {\r\n document.msExitFullscreen();\r\n }\r\n }\r\n};\r\n\r\nexport { openFullScreen, closeFullScreen };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getSessionData } from \"redux/reducers/selectors\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport {\r\n getUserId,\r\n getUserGuid,\r\n getFirstName,\r\n getLastName,\r\n} from \"redux/reducers/session/user/selectors\";\r\nimport {\r\n getScormServer,\r\n getUseLocalScormApi,\r\n} from \"redux/reducers/settings/app/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\n\r\nimport { eLearningApi } from \"libs/api/interface/api-e-learning\";\r\n\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport Button from \"@material-ui/core/Button\";\r\n\r\nimport { LoadingSpinner } from \"components/presentation/loading-spinner\";\r\nimport { Console, LOG_TYPES } from 'custom/console';\r\n\r\nimport { CourseTitle } from \"./title\";\r\nimport { getInitialScormData, getCourseDataFromSaveResponse } from \"../helper\";\r\nimport { SaveCounter } from \"./save-counter\";\r\nimport {openFullScreen, closeFullScreen} from './full-screen'\r\n\r\nconst styles = ({ breakpoints, spacing }) => ({\r\n layout: {\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n height: \"100%\",\r\n },\r\n iframeContainer: {\r\n flex: 1,\r\n display: \"flex\",\r\n backgroundColour: \"lime\",\r\n },\r\n iframe: {\r\n margin: `0px ${spacing.unit * 2}px`,\r\n display: \"block\",\r\n width: `calc(100% - ${spacing.unit * 4}px)`,\r\n height: `calc(100% - ${spacing.unit * 2}px)`,\r\n [breakpoints.down(\"xs\")]: {\r\n width: \"100%\",\r\n margin: \"0px\",\r\n height: \"56.25vw\",\r\n },\r\n },\r\n courseTitle: {\r\n margin: `${spacing.unit * 2}px ${spacing.unit * 2}px 0px ${\r\n spacing.unit * 2\r\n }px`,\r\n [breakpoints.down(\"xs\")]: {\r\n margin: `${spacing.unit * 2}px 0px 0px 0px`,\r\n },\r\n },\r\n visible: {\r\n visibility: \"visible\",\r\n },\r\n hidden: {\r\n visibility: \"hidden\",\r\n },\r\n});\r\n\r\nconst CONSTANTS = {\r\n WINDOW: \"window\",\r\n IFRAME: \"iframe\",\r\n SAVE_DATA: \"saveData\",\r\n SAVE_DATA_CLOSE: \"saveDataClose\",\r\n CLOSE: \"close\",\r\n READY: \"playerReady\",\r\n SET_PARAMETERS: \"setParameters\",\r\n};\r\n\r\nclass PlaySco extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleMessageFromPlayer = (evt) => {\r\n const { data } = evt;\r\n const { type } = data;\r\n\r\n if (type === CONSTANTS.READY) {\r\n const message = {\r\n type: CONSTANTS.SET_PARAMETERS,\r\n parameters: this.getPlayerParameters(),\r\n };\r\n this.sendMessageToPlayer(message);\r\n this.loadingTimer = setTimeout(() => {\r\n this.setState({ loading: false });\r\n }, 2000);\r\n } else if (type === \"saveData\") {\r\n this.saveScormData(data.data);\r\n } else if (type === \"saveDataClose\") {\r\n debugger;\r\n this.saveScormData(data.data, true);\r\n } else if (type === \"close\") {\r\n this.closePlayer();\r\n }\r\n };\r\n\r\n this.sendMessageToPlayer = (data = {}) => {\r\n //window.parent.postMessage(data, \"*\");\r\n if (this.scormType === CONSTANTS.IFRAME) {\r\n this.waitIframe(data);\r\n } else this.scoPlayer.postMessage(data, \"*\");\r\n };\r\n\r\n this.waitIframe = (data) => {\r\n if (!this.scoPlayer) {\r\n Console.log('No scoPlayer', LOG_TYPES.TEMP);\r\n Console.log(data, LOG_TYPES.TEMP);\r\n // this.waitIframeTimer = setTimeout(() => {\r\n // this.waitIframe(data);\r\n // }, 5000);\r\n } else {\r\n this.scoPlayer.contentWindow.postMessage(data, \"*\");\r\n }\r\n };\r\n\r\n this.resizeIframe = () => {\r\n this.setState({fullScreen: true});\r\n openFullScreen(this.scoPlayer);\r\n }\r\n\r\n this.saveScormData = this.saveScormData.bind(this);\r\n\r\n this.scormType = CONSTANTS.IFRAME;\r\n this.saveCounter = new SaveCounter();\r\n\r\n this.state = {\r\n initialData: null,\r\n waitingToClose: false,\r\n loading: true,\r\n fullScreen: false\r\n };\r\n }\r\n\r\n // componentDidMount() {\r\n // if (this.scormType === CONSTANTS.WINDOW) this.displayWindow();\r\n // }\r\n\r\n componentDidMount() {\r\n const { courseData, attemptId: scoAttemptId, userId } = this.props;\r\n const { attemptId: courseAttemptId } = courseData;\r\n\r\n const onSuccess = (response) => {\r\n const parsedResponse = JSON.parse(response);\r\n\r\n if (parsedResponse) {\r\n const {\r\n updateCourseData,\r\n userName: name,\r\n userId: id,\r\n } = this.props;\r\n const userInfo = { name, id };\r\n const { initialData, courseData } = getInitialScormData(\r\n parsedResponse,\r\n userInfo\r\n );\r\n\r\n this.setState({ initialData }, () => {\r\n if (courseData) updateCourseData(courseData);\r\n });\r\n }\r\n };\r\n\r\n const onFail = (data) => {\r\n debugger;\r\n };\r\n\r\n window.addEventListener(\"message\", this.handleMessageFromPlayer);\r\n\r\n eLearningApi.getScormData(scoAttemptId).then(onSuccess, onFail);\r\n }\r\n\r\n componentWillUnmount() {\r\n window.removeEventListener(\"message\", this.handleMessageFromPlayer);\r\n window.clearTimeout(this.waitIframeTimer);\r\n window.clearTimeout(this.loadingTimer);\r\n }\r\n\r\n saveScormData = (data, close = false) => {\r\n const localSaveCounter = parseInt(this.saveCounter.getCounter());\r\n\r\n const onSuccess = (response, close = false) => {\r\n this.saveCounter.removeCounter(localSaveCounter);\r\n const { waitingToClose } = this.state;\r\n const needToClosePlayer = close || waitingToClose;\r\n const parsedResponse = JSON.parse(response);\r\n\r\n if (parsedResponse) {\r\n const courseData = getCourseDataFromSaveResponse(\r\n parsedResponse\r\n );\r\n\r\n if (courseData) {\r\n const { updateCourseData } = this.props;\r\n updateCourseData(courseData).then(() => {\r\n if (needToClosePlayer) this.closePlayer();\r\n });\r\n } else {\r\n if (needToClosePlayer) this.closePlayer();\r\n }\r\n }\r\n };\r\n\r\n const onFail = (response) => {\r\n this.saveCounter.removeCounter(localSaveCounter);\r\n debugger;\r\n };\r\n\r\n const { attemptId: scoAttemptId } = this.props;\r\n\r\n eLearningApi.saveScormData(scoAttemptId, { data }).then((response) => {\r\n onSuccess(response, close);\r\n }, onFail);\r\n };\r\n\r\n closePlayer() {\r\n const {fullScreen} = this.state;\r\n if (fullScreen) closeFullScreen(this.scoPlayer);\r\n if (this.saveCounter.isEmpty()) {\r\n const { onCloseSco } = this.props;\r\n onCloseSco();\r\n } else {\r\n this.setState({ waitingToClose: true, fullScreen: false });\r\n }\r\n }\r\n\r\n getPlayerUrl() {\r\n const version = \"0.000003\";\r\n const player =\r\n this.scormType === CONSTANTS.WINDOW\r\n ? \"sco-player\"\r\n : \"sco-iframe-player\";\r\n const { scormServer } = this.props;\r\n // http://localhost/projects/sco_player\r\n const url = `${scormServer}/player/${player}.html?v=${version}`;\r\n\r\n return url;\r\n }\r\n\r\n getPlayerParameters() {\r\n const { initialData } = this.state;\r\n const { href, height } = this.props;\r\n\r\n return {\r\n initialData,\r\n url: href,\r\n height,\r\n debug: 1,\r\n };\r\n }\r\n\r\n displayIframe() {\r\n const { initialData, waitingToClose } = this.state;\r\n if (!initialData) {\r\n return ;\r\n }\r\n\r\n const { classes } = this.props;\r\n const url = this.getPlayerUrl();\r\n const divClass = `${classes.iframeContainer} ${\r\n waitingToClose ? classes.hidden : classes.visible\r\n }`;\r\n\r\n return (\r\n \r\n
\r\n );\r\n }\r\n\r\n displayWindow() {\r\n const url = this.getPlayerUrl();\r\n\r\n this.scoPlayer = window.open(url, \"_blank\");\r\n this.scoPlayer.focus();\r\n }\r\n\r\n get FullButton() {\r\n const buttonProps = {\r\n variant: \"contained\",\r\n color: \"secondary\",\r\n size: \"small\",\r\n key: 'viewFullScreen',\r\n onClick: () => this.resizeIframe(0),\r\n };\r\n\r\n return View Full Screen ;\r\n } \r\n\r\n get Buttons() {\r\n return [this.FullButton];\r\n } \r\n\r\n render() {\r\n const { waitingToClose, loading } = this.state;\r\n const {\r\n name,\r\n status,\r\n dateStarted,\r\n dateEnded,\r\n classes,\r\n courseData,\r\n } = this.props;\r\n const { name: courseName } = courseData;\r\n\r\n let message = null;\r\n if (loading) message = \"Loading\";\r\n else if (waitingToClose) message = \"Updating\";\r\n\r\n const courseProps = {\r\n name,\r\n status,\r\n dateStarted,\r\n dateEnded,\r\n courseName,\r\n message,\r\n buttons: this.Buttons\r\n };\r\n\r\n return (\r\n \r\n
\r\n { }\r\n
\r\n {this.scormType === CONSTANTS.IFRAME && this.displayIframe()}\r\n
\r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const userSessionData = getUserSessionData(getSessionData(store));\r\n const appSettingsData = getAppSettingsData(getSettingsData(store));\r\n const firstName = getFirstName(userSessionData);\r\n const lastName = getLastName(userSessionData);\r\n const userName = `${firstName} ${lastName}`;\r\n return {\r\n userId: getUserId(userSessionData),\r\n userGuid: getUserGuid(userSessionData),\r\n userName,\r\n scormServer: getScormServer(appSettingsData),\r\n };\r\n};\r\n\r\nPlaySco = connect(mapStoreToProps)(PlaySco);\r\n\r\nPlaySco = withStyles(styles)(PlaySco);\r\n\r\nexport { PlaySco };\r\n","import React, { Component } from \"react\";\r\n\r\nimport { withMessages } from \"components/hocs/messages\";\r\n\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nconst styles = ({ palette, spacing, breakpoints }) => ({\r\n root: {\r\n width: \"100%\",\r\n // maxWidth: 360,\r\n backgroundColor: palette.background.paper,\r\n },\r\n paper: {\r\n padding: spacing.unit *2,\r\n marginBottom: spacing.unit * 2,\r\n '&>div:first-child':{\r\n paddingBottom: spacing.unit * 2,\r\n }\r\n }\r\n});\r\n\r\nconst completeMessageId = MESSAGE_IDS.ELEARNING.COMPLETE;\r\nconst completeReturnMessageId = MESSAGE_IDS.ELEARNING.COMPLETE_RETURN;\r\nconst schedulesMessageId = MESSAGE_IDS.SCHEDULE.APP_BAR_TITLE;\r\n\r\nclass CourseInfoMessage extends Component {\r\n state = {};\r\n\r\n componentDidMount(){\r\n }\r\n\r\n render() {\r\n const { info } = this.props;\r\n if (!info) return null;\r\n \r\n const { classes } = this.props;\r\n\r\n return (\r\n \r\n {info}
\r\n \r\n );\r\n }\r\n}\r\n\r\nCourseInfoMessage = withStyles(styles)(CourseInfoMessage);\r\n\r\nCourseInfoMessage = withMessages(CourseInfoMessage);\r\n\r\nexport { CourseInfoMessage };\r\n","import React, { Component } from \"react\";\r\n\r\nimport { withMessages } from \"components/hocs/messages\";\r\n\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\nimport DoneIcon from \"@material-ui/icons/Done\";\r\nimport List from \"@material-ui/core/List\";\r\nimport ListItem from \"@material-ui/core/ListItem\";\r\nimport ListItemText from \"@material-ui/core/ListItemText\";\r\nimport ListItemAvatar from \"@material-ui/core/ListItemAvatar\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nconst styles = ({ palette, spacing, breakpoints }) => ({\r\n root: {\r\n width: \"100%\",\r\n // maxWidth: 360,\r\n backgroundColor: palette.background.paper,\r\n },\r\n paper: {\r\n // padding: spacing.unit * 2,\r\n marginBottom: spacing.unit * 2,\r\n \"&>div:first-child\": {\r\n paddingBottom: spacing.unit * 2,\r\n },\r\n },\r\n info: {\r\n padding: spacing.unit * 2,\r\n },\r\n avatar: {\r\n background: palette.primary.main,\r\n },\r\n});\r\n\r\nconst completeMessageId = MESSAGE_IDS.ELEARNING.COMPLETE;\r\nconst completeReturnMessageId = MESSAGE_IDS.ELEARNING.COMPLETE_RETURN;\r\nconst schedulesMessageId = MESSAGE_IDS.SCHEDULE.APP_BAR_TITLE;\r\n\r\nclass CourseCompleteMessage extends Component {\r\n state = {};\r\n\r\n componentDidMount() {}\r\n\r\n get Message() {\r\n const { classes } = this.props;\r\n const { completeMessage, returnMessage } = this.Messages;\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n \r\n \r\n {returnMessage} \r\n
\r\n \r\n );\r\n }\r\n\r\n get OldMessage() {\r\n const { completeMessage, returnMessage } = this.Messages;\r\n\r\n return (\r\n \r\n \r\n {completeMessage} \r\n
\r\n \r\n {returnMessage} \r\n
\r\n \r\n );\r\n }\r\n\r\n get Messages() {\r\n const { name, messages } = this.props;\r\n const schedules = messages[schedulesMessageId].toLowerCase();\r\n const completeMessage = messages[completeMessageId].replace(\r\n \"~name~\",\r\n name\r\n );\r\n const returnMessage = messages[completeReturnMessageId].replace(\r\n \"~schedules~\",\r\n schedules\r\n );\r\n\r\n return { completeMessage, returnMessage };\r\n }\r\n\r\n render() {\r\n const { showCompletedMessage } = this.props;\r\n if (!showCompletedMessage) return null;\r\n\r\n const { classes } = this.props;\r\n\r\n return {this.Message} ;\r\n }\r\n}\r\n\r\nCourseCompleteMessage = withStyles(styles)(CourseCompleteMessage);\r\n\r\nCourseCompleteMessage = withMessages(CourseCompleteMessage);\r\n\r\nexport { CourseCompleteMessage };\r\n","import React, { Component } from \"react\";\r\nimport { withRouter } from \"react-router-dom\";\r\n\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\n\r\nimport Fade from \"@material-ui/core/Fade\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nimport { CourseTitle } from \"./title\";\r\nimport { CourseScos } from \"./scos\";\r\nimport { PlaySco } from \"./play-sco\";\r\nimport { CONSTANTS } from \"./constants\";\r\nimport { CourseInfoMessage } from \"./info-message\";\r\nimport { CourseCompleteMessage } from \"./complete-message\";\r\n\r\nconst styles = ({ spacing, breakpoints, palette }) => ({\r\n layout: {\r\n width: \"auto\",\r\n display: \"block\", // Fix IE11 issue.\r\n marginLeft: spacing.unit * 2,\r\n marginRight: spacing.unit * 2,\r\n paddingTop: spacing.unit * 2,\r\n paddingBottom: spacing.unit * 2,\r\n [breakpoints.up(800 + spacing.unit * 3 * 2)]: {\r\n width: 800,\r\n marginLeft: \"auto\",\r\n marginRight: \"auto\",\r\n },\r\n [breakpoints.down(\"xs\")]: {\r\n marginLeft: 0,\r\n marginRight: 0,\r\n },\r\n },\r\n});\r\n\r\nclass Course extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handlePlaySco = this.handlePlaySco.bind(this);\r\n this.handleCloseSco = this.handleCloseSco.bind(this);\r\n this.displaySco = this.displaySco.bind(this);\r\n this.handleReturnToSchedules = this.handleReturnToSchedules.bind(this);\r\n\r\n\r\n this.state = {\r\n display: CONSTANTS.COURSE,\r\n scoIndex: null,\r\n showCompletedMessage: false,\r\n };\r\n }\r\n\r\n componentDidUpdate(prevProps) {\r\n const { status: prevStatus } = prevProps;\r\n const { status } = this.props;\r\n\r\n if (status === 2 && prevStatus !== 2) {\r\n this.setState({ showCompletedMessage: true });\r\n }\r\n }\r\n\r\n handlePlaySco(scoIndex) {\r\n if (check.assigned(scoIndex)) {\r\n this.setState({ display: CONSTANTS.SCO, scoIndex });\r\n }\r\n }\r\n\r\n handleCloseSco() {\r\n this.setState({ display: CONSTANTS.COURSE, scoIndex: null });\r\n }\r\n\r\n checkIfCourseHasJustCompleted() {\r\n const { completed } = this.state;\r\n if (completed) return false;\r\n\r\n const { scos } = this.props;\r\n return scos.every((sco) => sco.status === 2);\r\n }\r\n\r\n handleReturnToSchedules() {\r\n const { history } = this.props;\r\n\r\n history.replace(\"/schedules\");\r\n }\r\n\r\n get CourseContents() {\r\n const { display } = this.state;\r\n\r\n return display === CONSTANTS.SCO ? this.displaySco() : this.Course;\r\n }\r\n\r\n get Course() {\r\n const { showCompletedMessage } = this.state;\r\n\r\n const {\r\n name,\r\n status,\r\n info,\r\n dateStarted,\r\n dateEnded,\r\n classes,\r\n scos,\r\n updateCourseData,\r\n attemptId: courseAttemptId,\r\n } = this.props;\r\n\r\n const titleProps = {\r\n name,\r\n info,\r\n status,\r\n dateStarted,\r\n dateEnded,\r\n courseAttemptId,\r\n onReturnToSchedules: this.handleReturnToSchedules,\r\n updateCourseData,\r\n page: CONSTANTS.COURSE,\r\n };\r\n\r\n const infoProps = {\r\n name,\r\n info,\r\n showCompletedMessage,\r\n };\r\n\r\n const completeProps = {\r\n name,\r\n showCompletedMessage,\r\n };\r\n\r\n const scosProps = {\r\n onPlaySco: this.handlePlaySco,\r\n updateCourseData,\r\n showCompletedMessage,\r\n scos,\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n );\r\n }\r\n\r\n displaySco() {\r\n const { scoIndex } = this.state;\r\n if (!check.assigned(scoIndex)) return null;\r\n\r\n const {\r\n scos,\r\n name,\r\n dateStarted,\r\n dateEnded,\r\n status,\r\n attemptId,\r\n updateCourseData,\r\n } = this.props;\r\n //debugger;\r\n const scoData = scos[scoIndex];\r\n\r\n const _courseData = { name, dateStarted, dateEnded, status, attemptId };\r\n const scoProps = {\r\n courseData: _courseData,\r\n updateCourseData,\r\n onCloseSco: this.handleCloseSco,\r\n onReturnToSchedules: this.handleReturnToSchedules,\r\n ...scoData,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n render() {\r\n const { name } = this.props;\r\n\r\n const appBarProps = {\r\n title: name,\r\n logout: true,\r\n logo: true,\r\n loadingTitle: false,\r\n };\r\n\r\n return (\r\n \r\n \r\n {this.CourseContents}\r\n \r\n );\r\n }\r\n}\r\n\r\nCourse = withStyles(styles)(Course);\r\nCourse = withRouter(Course);\r\n\r\nexport { Course };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nimport { getSessionData } from \"redux/reducers/selectors\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getUserId, getUserGuid, getLanguageId } from \"redux/reducers/session/user/selectors\";\r\nimport {getOrgId} from 'redux/reducers/session/client/selectors'\r\nimport {getClientSessionData} from 'redux/reducers/session/selectors'\r\n\r\n\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\n\r\nimport { eLearningApi } from \"libs/api/interface/api-e-learning\";\r\n\r\nimport { Course } from \"./course\";\r\n\r\nimport { getCourseData } from \"../helper\";\r\n\r\nclass CoursePage extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n const {\r\n match: { params },\r\n } = props;\r\n\r\n this.courseAttemptGuid = check.assigned(params.elearningGuid)\r\n ? params.elearningGuid\r\n : null;\r\n\r\n this.handleUpdateCourseData = this.handleUpdateCourseData.bind(this);\r\n\r\n this.state = { data: null };\r\n }\r\n\r\n componentDidMount() {\r\n const onCourseInfoSuccess=(response)=>{\r\n const parsedReponse = JSON.parse(response);\r\n const data={info: parsedReponse[0], ...this.courseData};\r\n\r\n this.setState({ data });\r\n }\r\n\r\n const onSuccess = (response) => {\r\n const parsedReponse = JSON.parse(response);\r\n const data = getCourseData(parsedReponse);\r\n\r\n if (data){\r\n if (!data.courseInfoTextId){\r\n this.setState({ data });\r\n }\r\n else{\r\n this.courseData = data;\r\n\r\n const {orgID, langID} = this.props;\r\n const payload = {orgID, langID, iDs:[data.courseInfoTextId]}\r\n\r\n eLearningApi.getCourseInfo(payload).then(onCourseInfoSuccess, onFail);\r\n }\r\n }\r\n };\r\n\r\n const onFail = (data) => {\r\n debugger;\r\n };\r\n\r\n if (this.courseAttemptGuid) {\r\n eLearningApi\r\n .getCourseAttemptDetails(this.courseAttemptGuid)\r\n .then(onSuccess, onFail);\r\n }\r\n }\r\n\r\n handleUpdateCourseData(data) {\r\n return new Promise((resolve) => {\r\n this.setState({ data }, () => {\r\n resolve();\r\n });\r\n });\r\n }\r\n\r\n displayCourses() {\r\n const { data } = this.state;\r\n if (!data) return null;\r\n\r\n const courseProps = {\r\n updateCourseData: this.handleUpdateCourseData,\r\n ...data,\r\n };\r\n\r\n return ;\r\n }\r\n\r\n playSco() {}\r\n\r\n render() {\r\n const { data } = this.state;\r\n const title = check.assigned(data) ? data.name : \"Loading course\";\r\n const appBarProps = {\r\n title,\r\n logout: true,\r\n logo: true,\r\n loadingTitle: !check.assigned(data),\r\n };\r\n\r\n return (\r\n \r\n \r\n {this.displayCourses()}\r\n \r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const sessionData = getSessionData(store);\r\n const userSessionData = getUserSessionData(sessionData);\r\n\r\n return {\r\n userId: getUserId(userSessionData),\r\n orgID: getOrgId(getClientSessionData(sessionData)),\r\n langID: getLanguageId(userSessionData),\r\n userGuid: getUserGuid(userSessionData)\r\n };\r\n};\r\n\r\nCoursePage = connect(mapStoreToProps)(CoursePage);\r\n\r\nexport { CoursePage };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\nimport { withRouter } from \"react-router-dom\";\r\n\r\nimport { getUrlToken } from \"redux/reducers/session/client/selectors\";\r\nimport { getSessionData } from \"redux/reducers/selectors\";\r\nimport {\r\n getClientSessionData,\r\n getUserSessionData,\r\n} from \"redux/reducers/session/selectors\";\r\nimport { isAdmin } from \"redux/reducers/session/user/selectors\";\r\n\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\nimport { LoadingSpinner } from \"components/presentation/loading-spinner\";\r\n\r\nclass AdminPage extends Component {\r\n state = {};\r\n\r\n componentDidMount() {\r\n const { urlToken } = this.props;\r\n const adminUrl = 'https://dev.xams.co.uk/user/login/login';\r\n const url = `${adminUrl}?${urlToken}`;\r\n //debugger;\r\n //window.location.replace(url);\r\n }\r\n\r\n render() {\r\n const appBarProps = {\r\n title: \"Redirecting to admin\",\r\n loadingTitle: true,\r\n logo: true\r\n };\r\n\r\n return (\r\n \r\n \r\n ;\r\n \r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const sessionData = getSessionData(store);\r\n const userSessionData = getUserSessionData(sessionData);\r\n const clientSessionData = getClientSessionData(sessionData);\r\n const urlToken = getUrlToken(clientSessionData);\r\n const admin = isAdmin(userSessionData);\r\n\r\n return { urlToken, admin };\r\n};\r\n\r\nAdminPage = connect(mapStoreToProps)(AdminPage);\r\nAdminPage = withRouter(AdminPage);\r\n\r\nexport {AdminPage};\r\n","\r\nconst styles = theme => ({\r\n\tscroller: {\r\n\t\theight: '100%',\r\n\t\toverflowY: 'scroll',\r\n\t\tpaddingBottom: 200,\r\n\t},\r\n\tlayout: {\r\n\t width: 'auto',\r\n\t display: 'block', // Fix IE11 issue.\r\n\t marginLeft: theme.spacing.unit * 2,\r\n\t\tmarginRight: theme.spacing.unit * 2,\r\n\t [theme.breakpoints.up(800 + theme.spacing.unit * 3 * 2)]: {\r\n\t\t\twidth: 800,\r\n\t\t\tmarginLeft: 'auto',\r\n\t\t\tmarginRight: 'auto',\r\n\t\t},\r\n\t},\r\n\tform: {\r\n\t\tpadding: theme.spacing.unit * 2,\r\n\t\tbackgroundColor: theme.palette.background.light\r\n\t},\r\n\tchecks: {\r\n\t\tmarginTop: theme.spacing.unit * 1,\r\n\t\tfontWeight: 'bold'\r\n\t},\r\n\trubricTextContainer: {\r\n\t\tmarginBottom: theme.spacing.unit * 2\r\n\t},\r\n\trubricText: {\r\n\t\tcolor: theme.palette.background.contrastText\r\n\t},\r\n\trubricPaper: {\r\n\t\tbackgroundColor: theme.palette.background.main,\r\n\t\tpadding: theme.spacing.unit,\r\n\t\tflexGrow: 1,\r\n\t},\r\n\tbuttons: {\r\n\t\ttextAlign: 'right'\r\n\t},\r\n\tbutton: {\r\n\t\tbackgroundColor: theme.palette.primary.main + \"!important\",\r\n\t\tcolor: theme.palette.primary.contrastText + \"!important\"\r\n\t},\r\n\tbuttonDisabled: {\r\n\t\tbackgroundColor: theme.palette.background.light + \"!important\",\r\n\t\tcolor: theme.palette.background.contrastText + \"!important\"\r\n\t}\r\n});\r\n\r\n\r\nexport {styles}","import React, { Component } from \"react\";\r\n\r\nimport DateFnsUtils from \"@date-io/date-fns\";\r\nimport { DatePicker } from \"material-ui-pickers\";\r\nimport Checkbox from \"@material-ui/core/Checkbox\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nconst datesAreTheSame = (_date1, _date2) => {\r\n const date1 = new Date(_date1);\r\n const date2 = new Date(_date2);\r\n\r\n if (date1.getDate() !== date2.getDate()) return false;\r\n if (date1.getMonth() !== date2.getMonth()) return false;\r\n if (date1.getFullYear() !== date2.getFullYear()) return false;\r\n\r\n return true;\r\n};\r\n\r\nconst styles = () => ({\r\n root: {\r\n marginLeft: \"-2px\",\r\n display: \"flex\",\r\n width: \"100%\",\r\n \"&>div:last-child\": {\r\n flex: 1,\r\n },\r\n },\r\n checkBox: {\r\n margin: 0,\r\n paddingLeft: 0,\r\n },\r\n datePicker: {\r\n width: \"100%\",\r\n },\r\n});\r\n\r\nclass ConfirmDob extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleDateChange = this.handleDateChange.bind(this);\r\n\r\n this.state = { date: new Date(), match: false };\r\n }\r\n\r\n handleDateChange(value) {\r\n const { dob, onSelect } = this.props;\r\n const match = datesAreTheSame(value, dob);\r\n this.setState({ date: value, match });\r\n onSelect(null, match);\r\n }\r\n\r\n render() {\r\n const { date, match } = this.state;\r\n const { classes, label } = this.props;\r\n\r\n return (\r\n \r\n
\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n );\r\n }\r\n}\r\n\r\nConfirmDob = withStyles(styles)(ConfirmDob);\r\n\r\nexport { ConfirmDob };\r\n","import check from 'check-types'\r\n\r\nconst getLearnerIdentifierLabel = (learnerIdentifier) => {\r\n if (isLearnerIdentifierNI(learnerIdentifier)) {\r\n return \"National Insurance Number (NI)\";\r\n } else if (isLearnerIdentifierPPS(learnerIdentifier)) {\r\n return \"Personal Public Service Number (PPS)\";\r\n }\r\n\r\n return \"Learner Identifier\";\r\n};\r\n\r\nconst isLearnerIdentifierNI = (learnerIdentifier) => {\r\n const format = \"CCNNNNNNC\";\r\n\r\n return isFormat(learnerIdentifier, format);\r\n};\r\n\r\nconst isLearnerIdentifierPPS = (learnerIdentifier) => {\r\n const format = \"NNNNNNNCC\";\r\n\r\n return isFormat(learnerIdentifier, format);\r\n};\r\n\r\nconst isFormat = (_value, format) => {\r\n const value = removeSpaces(_value);\r\n\r\n if (check.nonEmptyString(value) && value.length === format.length) {\r\n return [...value].reduce((correct, character, index) => {\r\n if (correct) {\r\n const f = format[index];\r\n\r\n if (f === \"N\" && !isCharacterNumber(character)) {\r\n correct = false;\r\n } else if (f === \"C\" && !isCharacterAlphabetic(character)) {\r\n correct = false;\r\n }\r\n }\r\n\r\n return correct;\r\n }, true);\r\n }\r\n\r\n return false;\r\n};\r\n\r\nconst isCharacterNumber = (value) => {\r\n return value >= \"0\" && value <= \"9\";\r\n};\r\n\r\nconst isCharacterAlphabetic = (value) => {\r\n return (value >= \"a\" && value <= \"z\") || (value >= \"A\" && value <= \"Z\");\r\n};\r\n\r\nconst removeSpaces = (value) => {\r\n // return value.replace(/ /g, \"\");\r\n return [...value].reduce((newValue, character) => {\r\n if (isCharacterNumber(character) || isCharacterAlphabetic(character)) {\r\n newValue += character;\r\n }\r\n return newValue;\r\n }, \"\");\r\n};\r\n\r\nexport { getLearnerIdentifierLabel, removeSpaces };\r\n","import React, { Component } from \"react\";\r\n\r\nimport TextField from \"@material-ui/core/TextField\";\r\nimport Input from '@material-ui/core/Input'\r\nimport Checkbox from \"@material-ui/core/Checkbox\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nimport { removeSpaces } from \"./learner-identification-helper\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport CheckBoxIcon from '@material-ui/icons/CheckBox';\r\nimport CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';\r\n\r\n\r\nconst styles = ({spacing}) => ({\r\n root: {\r\n marginLeft: \"-2px\",\r\n display: \"flex\",\r\n width: \"100%\",\r\n \"&>div:last-child\": {\r\n flex: 1,\r\n width: \"100%\",\r\n marginTop:\"2px\"\r\n },\r\n \"&>div:nth-child(2)\": {\r\n marginRight: spacing.unit,\r\n marginTop:spacing.unit*1, \r\n },\r\n \"&>div:nth-child(1)\": {\r\n marginRight: spacing.unit,\r\n marginTop:spacing.unit*1, \r\n }, \r\n }\r\n});\r\n\r\nclass ConfirmLearnerIdentifierText extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleChange = this.handleChange.bind(this);\r\n\r\n this.state = { value: \"\", match: false };\r\n }\r\n\r\n handleChange(e) {\r\n const {match:prevMatch}=this.state;\r\n const { onChange, identifier } = this.props;\r\n\r\n const value = e.target.value;\r\n const match = removeSpaces(value).toLowerCase() === removeSpaces(identifier).toLowerCase();\r\n\r\n this.setState({ value, match }, () => {\r\n if (match!==prevMatch) onChange(null, match);\r\n });\r\n }\r\n\r\n displayCheckBox(){\r\n const {match}=this.state;\r\n const props={color:\"primary\"}\r\n return match? : \r\n }\r\n\r\n render() {\r\n const { value, match } = this.state;\r\n const { label, classes, textClass } = this.props;\r\n const textProps = { onChange: this.handleChange, value };\r\n\r\n return (\r\n \r\n
\r\n {this.displayCheckBox()}\r\n {/* */}\r\n
\r\n
\r\n \r\n {label}\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n );\r\n }\r\n}\r\n\r\nConfirmLearnerIdentifierText = withStyles(styles)(ConfirmLearnerIdentifierText);\r\n\r\nexport { ConfirmLearnerIdentifierText };","import check from \"check-types\";\r\n\r\nconst addMessageParameters = (_message, _parameters) => {\r\n const parameters = check.array(_parameters) ? _parameters : [_parameters];\r\n let message = \"\" + _message;\r\n\r\n for (let i = 0; i < parameters.length; i++) {\r\n message = message.replace(\"@\", parameters[i]);\r\n }\r\n\r\n return message;\r\n};\r\n\r\nexport { addMessageParameters };\r\n","import React, { Component } from \"react\";\r\n\r\nimport { ConfirmLearnerIdentifierText } from \"./confirm-learner-identifier-text\";\r\nimport { getLearnerIdentifierLabel } from \"./learner-identification-helper\";\r\n\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport FormControlLabel from \"@material-ui/core/FormControlLabel\";\r\nimport Checkbox from \"@material-ui/core/Checkbox\";\r\n\r\nimport { addMessageParameters } from \"utils/message-helper\";\r\n\r\nclass ConfirmLearnerIdentifier extends Component {\r\n state = {};\r\n\r\n get Checkbox() {\r\n const { onChange } = this.props;\r\n return ;\r\n }\r\n\r\n render() {\r\n const {\r\n identifier,\r\n confirmMode,\r\n onChange,\r\n textClass,\r\n confirmMessage,\r\n enterMessage,\r\n } = this.props;\r\n\r\n if (confirmMode === 2) {\r\n const label = addMessageParameters(\r\n enterMessage,\r\n getLearnerIdentifierLabel(identifier)\r\n );\r\n const props = {\r\n onChange,\r\n identifier,\r\n label,\r\n textClass\r\n };\r\n\r\n return ;\r\n } else if (confirmMode === 1) {\r\n const labelText = addMessageParameters(confirmMessage, [\r\n getLearnerIdentifierLabel(identifier),\r\n identifier,\r\n ]);\r\n const label = (\r\n \r\n {labelText}\r\n \r\n );\r\n\r\n return ;\r\n }\r\n\r\n return null;\r\n }\r\n}\r\n\r\nexport { ConfirmLearnerIdentifier };","// npm\r\nimport React from \"react\";\r\nimport Moment from \"react-moment\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\n\r\n// material-ui\r\nimport { styles } from \"./form-styles\";\r\nimport Fade from \"@material-ui/core/Fade\";\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport Button from \"@material-ui/core/Button\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\nimport Checkbox from \"@material-ui/core/Checkbox\";\r\nimport FormGroup from \"@material-ui/core/FormGroup\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport ArrowForwardIcon from \"@material-ui/icons/ArrowForward\";\r\nimport FormControlLabel from \"@material-ui/core/FormControlLabel\";\r\n\r\n// react\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\n// redux (selectors)\r\nimport { getSessionData, getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport {\r\n getDateOfBirth,\r\n getLearnerIdentifier,\r\n} from \"redux/reducers/session/user/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport {\r\n getConfirmDobMode,\r\n getConfirmLearnerIdentifierMode,\r\n} from \"redux/reducers/settings/client/selectors\";\r\n\r\nimport { ConfirmDob } from \"./confirm-dob\";\r\nimport { ConfirmLearnerIdentifier } from \"./confirm_learner_identifier/confirm-learner-identifier\";\r\n\r\n// constants\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nconst nextMessageId = MESSAGE_IDS.GENERAL.NEXT;\r\nconst rubricMessageId = MESSAGE_IDS.RUBRIC.RUBRIC;\r\nconst rubricIntroMessageId = MESSAGE_IDS.RUBRIC.INTRO;\r\nconst confirmNameMessageId = MESSAGE_IDS.RUBRIC.CONFIRM_NAME;\r\nconst confirmInstructionsMessageId = MESSAGE_IDS.RUBRIC.CONFIRM_INSTRUCTIONS;\r\n\r\nclass RubricForm extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.state = { checkedCount: 0 };\r\n //this.checkboxCount = this.NeedsToConfirmDob ? 3 : 2;\r\n this.checkboxCount = 2;\r\n if (this.NeedsToConfirmDob) {\r\n this.checkboxCount++;\r\n }\r\n if (this.NeedsToConfirmLearnerIdentifier) {\r\n this.checkboxCount++;\r\n }\r\n\r\n this.initializeBoundMethods();\r\n }\r\n\r\n initializeBoundMethods() {\r\n this.onCheckboxChange = (_, value) => {\r\n const { checkedCount } = this.state;\r\n this.setState({\r\n checkedCount: value ? checkedCount + 1 : checkedCount - 1,\r\n });\r\n };\r\n }\r\n\r\n componentDidUpdate(_, prevState) {\r\n const { checkedCount } = this.state;\r\n const oldCheckedCount = prevState.checkedCount;\r\n\r\n if (checkedCount === oldCheckedCount) {\r\n return;\r\n }\r\n\r\n if (checkedCount < 0 || checkedCount > this.checkboxCount) {\r\n throw `Check count error, ${checkedCount}, ${this.checkboxCount}`;\r\n }\r\n\r\n if (oldCheckedCount === this.checkboxCount) {\r\n this.props.onUnfilled();\r\n } else if (checkedCount === this.checkboxCount) {\r\n this.props.onFilled();\r\n }\r\n }\r\n\r\n render() {\r\n const { classes } = this.props;\r\n\r\n return (\r\n \r\n
\r\n \r\n {this.Rubric}\r\n \r\n {this.Agreement}\r\n {this.Button}\r\n \r\n \r\n
\r\n );\r\n }\r\n\r\n get Rubric() {\r\n const { classes, messages } = this.props;\r\n\r\n return (\r\n \r\n \r\n \r\n {messages[rubricIntroMessageId]}\r\n \r\n
\r\n \r\n
\r\n \r\n {messages[rubricMessageId]}\r\n \r\n \r\n
\r\n \r\n );\r\n }\r\n\r\n get Agreement() {\r\n return (\r\n \r\n \r\n {this.ConfirmMessage1}\r\n {this.ConfirmMessage2}\r\n {this.DateOfBirthConfirmMessage}\r\n {this.LearnerIdentifierConfirmMessage}\r\n \r\n
\r\n );\r\n }\r\n\r\n get ConfirmMessage1() {\r\n const label = (\r\n \r\n {this.props.messages[confirmNameMessageId]}\r\n \r\n );\r\n\r\n return ;\r\n }\r\n\r\n get ConfirmMessage2() {\r\n const label = (\r\n \r\n {this.props.messages[confirmInstructionsMessageId]}\r\n \r\n );\r\n\r\n return ;\r\n }\r\n\r\n get NeedsToConfirmDob() {\r\n const { dateOfBirth, confirmDobMode } = this.props;\r\n\r\n return (\r\n dateOfBirth &&\r\n confirmDobMode &&\r\n [1, 2].indexOf(confirmDobMode) !== -1\r\n );\r\n }\r\n\r\n get DateOfBirthConfirmMessage() {\r\n if (!this.NeedsToConfirmDob) return null;\r\n\r\n const { dateOfBirth, confirmDobMode } = this.props;\r\n\r\n if (confirmDobMode === 2) {\r\n const dobProps = {\r\n onSelect: this.onCheckboxChange,\r\n dob: dateOfBirth,\r\n label: \"Please select your Date of Birth\",\r\n };\r\n\r\n return ;\r\n } else if (confirmDobMode === 1) {\r\n const label = (\r\n \r\n {\"I confirm that my date of birth is: \"}\r\n \r\n \r\n );\r\n\r\n return ;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n get NeedsToConfirmLearnerIdentifier() {\r\n const { learnerIdentifier, confirmLearnerIdentifierMode } = this.props;\r\n\r\n return (\r\n learnerIdentifier &&\r\n confirmLearnerIdentifierMode &&\r\n [1, 2].indexOf(confirmLearnerIdentifierMode) !== -1\r\n );\r\n }\r\n\r\n get LearnerIdentifierConfirmMessage() {\r\n if (!this.NeedsToConfirmLearnerIdentifier) return null;\r\n\r\n const { learnerIdentifier, confirmLearnerIdentifierMode, classes } =\r\n this.props;\r\n\r\n const props = {\r\n identifier: learnerIdentifier,\r\n confirmMode: confirmLearnerIdentifierMode,\r\n onChange: this.onCheckboxChange,\r\n textClass: classes.rubricText,\r\n confirmMessage: \"I can confirm that my @ is: @\",\r\n enterMessage: \"Please enter your @\",\r\n };\r\n\r\n return ;\r\n }\r\n\r\n get Checkbox() {\r\n return ;\r\n }\r\n\r\n get Button() {\r\n const disabled = !this.props.onSubmit;\r\n const onClick = () => this.props.onSubmit();\r\n const { classes } = this.props;\r\n\r\n return (\r\n \r\n
\r\n {this.props.messages[nextMessageId]}\r\n \r\n \r\n \r\n
\r\n );\r\n }\r\n}\r\n\r\nRubricForm.propTypes = {\r\n onFilled: PropTypes.func,\r\n onUnfilled: PropTypes.func,\r\n onSubmit: PropTypes.func,\r\n messages: PropTypes.shape({\r\n [nextMessageId]: PropTypes.string.isRequired,\r\n [rubricMessageId]: PropTypes.string.isRequired,\r\n [rubricIntroMessageId]: PropTypes.string.isRequired,\r\n [confirmNameMessageId]: PropTypes.string.isRequired,\r\n [confirmInstructionsMessageId]: PropTypes.string.isRequired,\r\n }),\r\n};\r\n\r\nRubricForm = withStyles(styles)(RubricForm);\r\nRubricForm = withMessages(RubricForm);\r\n\r\nconst mapStoreToProps = (store) => {\r\n const sessionData = getSessionData(store);\r\n const userSessionData = getUserSessionData(sessionData);\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n return {\r\n dateOfBirth: getDateOfBirth(userSessionData),\r\n confirmDobMode: getConfirmDobMode(clientSettingsData),\r\n learnerIdentifier: getLearnerIdentifier(userSessionData),\r\n confirmLearnerIdentifierMode:\r\n getConfirmLearnerIdentifierMode(clientSettingsData),\r\n };\r\n};\r\n\r\nRubricForm = connect(mapStoreToProps)(RubricForm);\r\n\r\nexport { RubricForm };\r\n","\nimport {Machine} from 'xstate'\nimport {XStateConfig} from './xstate-config'\n\n\n// #########################################\n// STATE & EVENT NAMES\n// #########################################\n\nconst STATES = {\n\tUNCHECKED: 'unchecked',\n\tCHECKED: 'all_checked',\n\tFINISHED: 'finished'\n}\n\nconst EVENTS = {\n\tCHECKED: 'checkboxes.checked',\n\tUNCHECKED: 'checkboxes.unchecked',\n\tFINISHED: 'checkboxes.submitted'\n}\n\n\n// #########################################\n// RUBRIC STATES\n// #########################################\n\nconst unchecked = new XStateConfig();\nunchecked.addTransition(EVENTS.CHECKED, STATES.CHECKED);\n\nconst checked = new XStateConfig();\nchecked.addTransition(EVENTS.UNCHECKED, STATES.UNCHECKED);\nchecked.addTransition(EVENTS.FINISHED, STATES.FINISHED);\n\nconst finished = new XStateConfig();\n\n\n// #########################################\n// RUBRIC MACHINE\n// #########################################\n\nconst _rubric = new XStateConfig();\n_rubric.initialState = STATES.UNCHECKED;\n_rubric.addState(STATES.CHECKED, checked);\n_rubric.addState(STATES.UNCHECKED, unchecked);\n_rubric.addState(STATES.FINISHED, finished);\n\nconst machine = Machine(_rubric.toObject());\nmachine.id = \"Rubric Machine\";\n\n\n// #########################################\n// EXPORT\n// #########################################\n\nconst rubric = {\n\tmachine,\n\tEVENTS: { ...EVENTS },\n\tSTATES: { ...STATES }\n}\n\nexport {rubric}","\r\n// npm\r\nimport React from 'react'\r\nimport {Redirect} from 'react-router-dom'\r\n\r\n// xams-components\r\nimport {StateMachine, StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {RubricForm} from './form'\r\n\r\n// machines\r\nimport {rubric} from 'machines/rubric'\r\n\r\n\r\nconst {STATES, EVENTS} = rubric;\r\n\r\n\r\nconst RubricFormMachine = () =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{{\r\n\t\t\t\t\t[STATES.UNCHECKED]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.CHECKED]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.FINISHED]: () => (\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t)\r\n\t\t\t\t}}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {RubricFormMachine}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {RubricFormMachine} from './machine'\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\r\n\r\n// constants\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst welcomeMessageId = MESSAGE_IDS.APP_BAR.WELCOME;\r\n\r\n\r\nlet RubricPage = ({messages}) =>\r\n{\r\n\tconst appBarProps = {\r\n\t\ttitle: messages[welcomeMessageId],\r\n\t\tlogout: true,\r\n\t\tlogo: true\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t \r\n\t\t\t
\r\n\t\t \r\n\t)\r\n}\r\n\r\nRubricPage.propTypes = {\r\n\tmessages: PropTypes.shape({\r\n\t\t[welcomeMessageId]: PropTypes.string.isRequired\r\n\t})\r\n}\r\n\r\nRubricPage = withMessages(RubricPage);\r\n\r\n\r\nexport {RubricPage}","\r\nconst styles = ({spacing, breakpoints, palette}) => ({\r\n\tscroller: {\r\n\t\theight: '100%',\r\n\t\toverflowY: 'scroll',\r\n\t\tpaddingBottom: 200,\r\n\t},\r\n\tlayout: {\r\n\t width: 'auto',\r\n\t display: 'block', // Fix IE11 issue.\r\n\t marginLeft: spacing.unit * 2,\r\n\t\tmarginRight: spacing.unit * 2,\r\n\t\tpaddingTop: spacing.unit * 2,\r\n\t [breakpoints.up(800 + spacing.unit * 3 * 2)]: {\r\n\t\t\twidth: 800,\r\n\t\t\tmarginLeft: 'auto',\r\n\t\t\tmarginRight: 'auto',\r\n\t\t},\r\n\t},\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t},\r\n\tform: {\r\n\t\tpadding: spacing.unit * 2,\r\n\t\tposition: 'relative',\r\n\t\tbackgroundColor: palette.background.light\r\n\t},\r\n\telearning: {\r\n\t\tmarginTop: spacing.unit * 2,\r\n\t\tmarginBottom: spacing.unit * 2,\r\n\t\tpadding: spacing.unit * 2,\r\n\t\tposition: 'relative',\r\n\t\tbackgroundColor: palette.background.light\r\n\t},\t\r\n\tschedulesText: {\r\n\t\tflexGrow: 1,\r\n\t\tpaddingLeft: 50,\r\n\t\tmarginBottom: spacing.unit\r\n\t},\r\n\tclockIcon: {\r\n\t\tfontSize: 38,\r\n\t\tleft: 15,\r\n\t\tmarginTop: 2,\r\n\t\tposition: 'absolute',\r\n\t\tdisplay: 'inline-block',\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t},\r\n\tscheduleButton: {\r\n\t\tbackgroundColor: palette.secondary.main + \"!important\",\r\n\t\tcolor: palette.secondary.contrastText + \"!important\",\r\n [breakpoints.up('sm')]: {\r\n width: 120,\r\n },\r\n [breakpoints.down('xs')]: {\r\n // paddingLeft: 0,\r\n minWidth: \"0px !important\",\r\n }, \t\t\r\n\r\n\t},\r\n\tbuttonDisabled: {\r\n\t\tbackgroundColor: palette.secondary.light + \"!important\",\r\n\t\tcolor: palette.secondary.contrastText + \"!important\"\r\n\t}\r\n});\r\n\r\nexport {styles}","\r\n// npm\r\nimport React from 'react'\r\nimport Moment from 'react-moment'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport {styles} from './styles'\r\nimport Typography from '@material-ui/core/Typography'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// redux (selectors)\r\nimport {getSessionData} from 'redux/reducers/selectors'\r\nimport {getUserGuid} from 'redux/reducers/session/user/selectors'\r\nimport {getUserSessionData} from 'redux/reducers/session/selectors'\r\n\r\n// utils\r\nimport {generalApi} from 'libs/api/interface/api-general'\r\n\r\n\r\nconst FAILED = 'failed';\r\n\r\nclass Clock extends React.Component {\r\n\r\n\tconstructor(props) \r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {\r\n\t\t\tformattedDateString: null,\r\n\t\t\tserverTime : null\r\n\t\t}\r\n\t}\r\n\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tthis.getServerDateTime();\r\n\t}\r\n\r\n\tcomponentWillUnmount() {\r\n clearTimeout(this.timer);\r\n }\r\n\r\n\thandleTimer() {\r\n\t\tvar delta = new Date().getTime() - this.timerStart;\r\n\t\t\r\n\t\tthis.setState({\r\n\t\t\tserverTime: new Date(this.serverStartTime.getTime() + delta)\r\n\t\t});\r\n }\r\n\r\n\tgetServerDateTime()\r\n\t{\r\n\t\tconst onSuccess = (response) => {\r\n\t\t\tvar parsedResponse = JSON.parse(response);\r\n\t\t\tthis.serverStartTime = new Date(parsedResponse.time);\r\n\t\t\tthis.setState({\r\n\t\t\t\tserverTime: this.serverStartTime,\r\n\t\t\t\tformattedDateString: parsedResponse.formattedDate\r\n\t\t\t});\r\n\t\t\tthis.timerStart = new Date().getTime();\r\n\t\t\tthis.timer = setInterval( () => {\r\n\t\t\t\tthis.handleTimer();\r\n\t\t\t}, 1000);\r\n\t\t}\r\n\r\n\t\tconst onError = (response) => {\r\n\t\t\tthis.setState({serverTime: FAILED});\r\n\t\t}\r\n\r\n\t\tgeneralApi.getTime(this.props.userGuid).then(onSuccess, onError);\r\n\t}\r\n\r\n\trender() \r\n\t{\r\n\t\tif (this.state.serverTime === null) {\r\n\t\t\treturn {\"Fetching server time...\"} ;\r\n\t\t}\r\n\r\n\t\tif (this.state.serverTime === FAILED) {\r\n\t\t\treturn {\"Failed to load server time\"} ;\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.state.formattedDateString || }\r\n\t\t\t\t{\", \"}\r\n\t\t\t\t\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n}\r\n\r\nClock.propTypes = {\r\n\tclasses: PropTypes.object.isRequired,\r\n\tuserGuid: PropTypes.string.isRequired\r\n}\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\tuserGuid: getUserGuid(getUserSessionData(getSessionData(store)))\r\n})\r\n\r\nClock = connect(mapStoreToProps)(Clock);\r\nClock = withStyles(styles)(Clock);\r\n\r\nexport {Clock}\r\n\r\n\r\n","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { withRouter } from \"react-router-dom\";\r\n\r\n// material-ui\r\nimport { styles } from \"./styles\";\r\nimport Hidden from \"@material-ui/core/Hidden\";\r\nimport Button from \"@material-ui/core/Button\";\r\nimport ListItem from \"@material-ui/core/ListItem\";\r\nimport ListItemText from \"@material-ui/core/ListItemText\";\r\nimport ArrowForward from \"@material-ui/icons/ArrowForward\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\n// redux (selectors)\r\nimport { getSchedulesData, getProctorioData } from \"redux/reducers/selectors\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getUserGuid } from \"redux/reducers/session/user/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getSessionData, getSettingsData } from \"redux/reducers/selectors\";\r\nimport * as SELECTORS from \"redux/reducers/schedules/schedule/selectors\";\r\nimport { getScheduleDataByIndex } from \"redux/reducers/schedules/selectors\";\r\nimport { isResetScheduleCheatActive } from \"redux/reducers/settings/app/selectors\";\r\nimport { getParent } from \"redux/reducers/proctorio/selectors\";\r\n\r\n// react\r\nimport { withMessages } from \"components/hocs/messages\";\r\n\r\n// other\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\nimport { assessmentApi } from \"libs/api/interface/api-assessment\";\r\nimport { PROCTORING_TYPES } from \"constants/proctoring\";\r\n\r\nconst resumeMessageId = MESSAGE_IDS.GENERAL.RESUME;\r\nconst startMessageId = MESSAGE_IDS.GENERAL.START;\r\n\r\n// ScheduleItem (not connected to store)\r\n// ------------------------------------------\r\n\r\nclass ScheduleItem extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n // Temporary cheat code (remove when no longer in use!!!)\r\n // ----------------------------------------------------------------\r\n this.state = { reset: false, resetting: false };\r\n\r\n this.handleCheat = () => {\r\n this.setState({ resetting: true });\r\n\r\n const args = [\r\n this.props.userGuid,\r\n this.props.scheduleGuid,\r\n this.props.scheduleType,\r\n this.props.examGuid,\r\n this.props.examTypeGuid,\r\n ];\r\n\r\n assessmentApi.resetSchedule(...args).then(() => {\r\n this.setState({ reset: true, resetting: false });\r\n });\r\n };\r\n // ----------------------------------------------------------------\r\n\r\n this.openExam = () => {\r\n if (this.needToStartProctorio()) {\r\n const {\r\n proctorProvider,\r\n proctoringSessionID,\r\n examGuid,\r\n } = this.props;\r\n\r\n this.props.history.push(\r\n `/proctoring/${proctorProvider}/${proctoringSessionID}/${examGuid}`\r\n );\r\n } else {\r\n const { examGuid } = this.props;\r\n\r\n this.props.history.push(`/exam/${examGuid}`);\r\n }\r\n };\r\n\r\n this.needToStartProctorio = () => {\r\n const { proctorProvider, proctorioActive } = this.props;\r\n\r\n return proctorProvider === PROCTORING_TYPES.PROCTORIO && !proctorioActive;\r\n };\r\n }\r\n\r\n render() {\r\n const { examName, classes, useResetScheduleCheat, completed } = this.props;\r\n const typographyProps = { className: classes.text };\r\n\r\n const listItemProps = completed\r\n ? { }\r\n : {\r\n onClick: this.openExam,\r\n button: true,\r\n };\r\n\r\n const listItemTextProps = {\r\n primaryTypographyProps: typographyProps,\r\n secondaryTypographyProps: typographyProps,\r\n primary: examName,\r\n secondary: this.SecondaryText,\r\n };\r\n\r\n return (\r\n \r\n
\r\n \r\n \r\n
\r\n {useResetScheduleCheat\r\n ? this.CheatAction\r\n : this.SecondaryAction}\r\n
\r\n
\r\n );\r\n }\r\n\r\n get SecondaryText() {\r\n if (this.props.practice) {\r\n return null;\r\n }\r\n if (this.props.scheduleInfo) {\r\n return this.props.scheduleInfo;\r\n }\r\n if (this.props.completed) {\r\n const message = 'Completed';\r\n return (\r\n \r\n \r\n \r\n
{message}
\r\n
{this.props.completedDtFmt}
\r\n
\r\n \r\n {`${message} - ${this.props.completedDtFmt}`} \r\n \r\n );\r\n }\r\n return this.props.startDtFmt;\r\n }\r\n\r\n get SecondaryAction() {\r\n if (this.props.completed) {\r\n return null;\r\n }\r\n\r\n return this.props.commenced\r\n ? this.getSecondaryAction(resumeMessageId)\r\n : this.getSecondaryAction(startMessageId);\r\n }\r\n\r\n getSecondaryAction(messageId) {\r\n const { classes } = this.props;\r\n\r\n const buttonProps = {\r\n className: classes.scheduleButton,\r\n classes: { disabled: classes.buttonDisabled },\r\n onClick: this.openExam,\r\n variant: \"contained\",\r\n color: \"primary\",\r\n size: \"small\",\r\n };\r\n\r\n return (\r\n \r\n {this.props.messages[messageId]} \r\n \r\n \r\n );\r\n }\r\n\r\n get CheatAction() {\r\n const buttonText = this.state.reset\r\n ? \"Reset! Reload page\"\r\n : this.state.resetting\r\n ? \"Resetting...\"\r\n : this.props.commenced\r\n ? \"Reset schedule\"\r\n : this.props.messages[startMessageId];\r\n\r\n const buttonProps = {\r\n disabled: this.state.reset || this.state.resetting,\r\n color: \"primary\",\r\n variant: \"contained\",\r\n size: \"small\",\r\n onClick: buttonText === \"Start\" ? this.openExam : this.handleCheat,\r\n };\r\n\r\n return {buttonText} ;\r\n }\r\n}\r\n\r\nScheduleItem.propTypes = {\r\n scheduleInfo: PropTypes.string,\r\n scheduleGuid: PropTypes.string.isRequired,\r\n scheduleType: PropTypes.string.isRequired,\r\n examName: PropTypes.string.isRequired,\r\n examGuid: PropTypes.string.isRequired,\r\n examTypeGuid: PropTypes.string.isRequired,\r\n startDt: PropTypes.string,\r\n startDtFmt: PropTypes.string,\r\n completedDt: PropTypes.string,\r\n completedDtFmt: PropTypes.string,\r\n commenced: PropTypes.bool.isRequired,\r\n completed: PropTypes.bool.isRequired,\r\n practice: PropTypes.bool.isRequired,\r\n requiresPassword: PropTypes.bool.isRequired,\r\n history: PropTypes.object.isRequired,\r\n userGuid: PropTypes.string.isRequired,\r\n useResetScheduleCheat: PropTypes.bool.isRequired,\r\n messages: PropTypes.shape({\r\n [resumeMessageId]: PropTypes.string.isRequired,\r\n [startMessageId]: PropTypes.string.isRequired,\r\n }),\r\n};\r\n\r\n// ScheduleItem (connected to store)\r\n// ------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store, { i }) => {\r\n const scheduleData = getScheduleDataByIndex(getSchedulesData(store))(i);\r\n const proctorioData = getProctorioData(store);\r\n\r\n return {\r\n i: undefined, // don't pass on 'i' prop as no longer needed\r\n scheduleGuid: SELECTORS.getScheduleGuid(scheduleData),\r\n scheduleType: SELECTORS.getScheduleType(scheduleData),\r\n scheduleInfo: SELECTORS.getScheduleInfo(scheduleData),\r\n examName: SELECTORS.getExamName(scheduleData),\r\n examGuid: SELECTORS.getExamGuid(scheduleData),\r\n examTypeGuid: SELECTORS.getExamTypeGuid(scheduleData),\r\n startDt: SELECTORS.getScheduleStartTime(scheduleData),\r\n startDtFmt: SELECTORS.getScheduleStartTimeFmt(scheduleData),\r\n completedDt: SELECTORS.getScheduleCompletedTime(scheduleData),\r\n completedDtFmt: SELECTORS.getScheduleCompletedTimeFmt(scheduleData),\r\n commenced: SELECTORS.hasScheduleCommenced(scheduleData),\r\n completed: SELECTORS.hasScheduleCompleted(scheduleData),\r\n practice: SELECTORS.isSchedulePractice(scheduleData),\r\n requiresPassword: SELECTORS.requiresSchedulePassword(scheduleData),\r\n userGuid: getUserGuid(getUserSessionData(getSessionData(store))),\r\n useResetScheduleCheat: isResetScheduleCheatActive(\r\n getAppSettingsData(getSettingsData(store))\r\n ),\r\n proctorProvider: SELECTORS.getProctorProvider(scheduleData),\r\n proctoringSessionID: SELECTORS.getProctoringSessionID(scheduleData),\r\n proctorioActive: getParent(proctorioData), \r\n };\r\n};\r\n\r\n// ScheduleItem (connected to router/styles/messages)\r\n// ------------------------------------------------------------------------\r\n\r\nScheduleItem = withRouter(connect(mapStoreToProps)(ScheduleItem));\r\nScheduleItem = withStyles(styles)(ScheduleItem);\r\nScheduleItem = withMessages(ScheduleItem);\r\n\r\nexport { ScheduleItem };\r\n","\nconst getElearning = (elearningData) => {\n\treturn elearningData.get('elearning').toJS();\n}\n\nconst getElearningCount = (eLearningData) => {\n\treturn eLearningData.get('elearning').size;\n}\n\nconst getShowCompleted = (eLearningData) => {\n\treturn eLearningData.get('showCompleted')\n}\n\nconst getShowCourseInfo = (eLearningData) => {\n\treturn eLearningData.get('showCourseInfo')\n}\n\nconst getCourseInfo = (eLearningData, courseId) => {\n\tdebugger;\n}\n\nexport {\n\tgetElearning,\n\tgetElearningCount,\n\tgetShowCompleted,\n\tgetShowCourseInfo,\n\tgetCourseInfo\n}","import React, { Component } from \"react\";\r\nimport {withRouter} from 'react-router-dom'\r\n\r\nimport ListItem from \"@material-ui/core/ListItem\";\r\nimport ListItemText from \"@material-ui/core/ListItemText\";\r\nimport ListItemAvatar from \"@material-ui/core/ListItemAvatar\";\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\nimport ListItemSecondaryAction from \"@material-ui/core/ListItemSecondaryAction\";\r\n\r\nimport { getStatusIcon, getStatusText, hasScormFailed } from \"./helper\";\r\nimport { PlayScormButton } from \"./course/play-scorm-button\";\r\n\r\n\r\nclass ElearningSchedule extends Component {\r\n state = {};\r\n\r\n get Icon() {\r\n const { status } = this.props;\r\n\r\n return getStatusIcon(status);\r\n }\r\n\r\n get PrimaryText() {\r\n const { courseName, name } = this.props;\r\n\r\n return name || courseName;\r\n }\r\n\r\n get SecondaryText() {\r\n const { status, dateStarted, dateEnded } = this.props;\r\n\r\n return getStatusText(status, dateStarted, dateEnded);\r\n }\r\n\r\n get SecondaryAction() {\r\n const { status } = this.props;\r\n\r\n if (hasScormFailed(status)) return null;\r\n\r\n const buttonProps = {\r\n status,\r\n onClick: this.openElearning\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n );\r\n }\r\n\r\n openElearning = () => {\r\n const { attemptGUID, guid } = this.props;\r\n const _guid = guid || attemptGUID;\r\n\r\n this.props.history.push(`/elearning/${_guid}`);\r\n };\r\n\r\n render() {\r\n const { status } = this.props;\r\n const isButton = !hasScormFailed(status);\r\n const eLearning = hasScormFailed(status)?null:this.openElearning;\r\n\r\n return (\r\n \r\n \r\n {this.Icon} \r\n \r\n \r\n {this.SecondaryAction}\r\n \r\n );\r\n }\r\n}\r\n\r\nElearningSchedule = withRouter(ElearningSchedule);\r\n\r\nexport { ElearningSchedule };\r\n","import React from \"react\";\r\n\r\nimport FormControlLabel from \"@material-ui/core/FormControlLabel\";\r\nimport Switch from \"@material-ui/core/Switch\";\r\nimport Hidden from \"@material-ui/core/Hidden\";\r\n\r\nconst capitalize = (s) => {\r\n if (typeof s !== \"string\") return \"\";\r\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\r\n};\r\n\r\nconst getShortMessage = (message) => {\r\n if (!message || typeof message !== \"string\") return message;\r\n\r\n const words = message.split(\" \");\r\n\r\n if (!words || words.length < 2) return message;\r\n\r\n return capitalize(words[words.length - 1].trim());\r\n};\r\n\r\nconst ElearningCompletedSwitch = (props) => {\r\n const { onChange, checked } = props;\r\n const message = \"Include completed\";\r\n const shortMessage = getShortMessage(message);\r\n return (\r\n \r\n }\r\n label={\r\n \r\n {message} \r\n {shortMessage} \r\n \r\n }\r\n labelPlacement=\"start\"\r\n />\r\n );\r\n};\r\n\r\nexport { ElearningCompletedSwitch };\r\n","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getElearningData } from \"redux/reducers/selectors\";\r\nimport { getElearning, getShowCompleted } from \"redux/reducers/elearning/selectors\";\r\nimport {setShowCompleted} from \"./actions\"\r\n\r\nimport { styles } from \"./styles\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\nimport { ElearningSchedule } from \"./elearning-schedule\";\r\nimport {ElearningCompletedSwitch} from './elearning-completed-switch'\r\n\r\nimport SchoolIcon from \"@material-ui/icons/School\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport List from \"@material-ui/core/List\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\nimport { checkForCompletedSwitch, getNotCompleted\r\n } from \"./helper\";\r\n\r\n\r\n\r\nclass ElearningSchedules extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.handleSwitchChange = this.handleSwitchChange.bind(this);\r\n\r\n const {eLearning, showCompletedValue} = props;\r\n const {hasCompletedSwitch, completedSwitch} = checkForCompletedSwitch(eLearning, showCompletedValue);\r\n this.hasCompletedSwitch = hasCompletedSwitch;\r\n\r\n this.state = { completedSwitch };\r\n }\r\n\r\n handleSwitchChange(event) {\r\n const {showCompleted} = this.props;\r\n const {checked} = event.target;\r\n\r\n showCompleted(checked);\r\n this.setState({ completedSwitch: checked });\r\n }\r\n\r\n get Title() {\r\n const { classes } = this.props;\r\n const { completedSwitch } = this.state;\r\n const message = \"Your E-Learning\";\r\n const switchProps = {\r\n checked: completedSwitch,\r\n onChange: this.handleSwitchChange,\r\n };\r\n\r\n return (\r\n \r\n
\r\n
\r\n {message}\r\n \r\n {this.hasCompletedSwitch &&
}\r\n
\r\n );\r\n }\r\n\r\n get ElearningList() {\r\n const { eLearning } = this.props;\r\n const { completedSwitch } = this.state;\r\n const displayElearning = completedSwitch?eLearning:getNotCompleted(eLearning);\r\n return (\r\n \r\n {displayElearning.map((_eLearning, index) => {\r\n const eLearningProps = {\r\n key: index,\r\n ..._eLearning,\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n );\r\n })}\r\n
\r\n );\r\n }\r\n\r\n render() {\r\n return (\r\n \r\n \r\n {this.Title}\r\n
\r\n {this.ElearningList}\r\n \r\n );\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const eLearningData = getElearningData(store);\r\n\r\n return{\r\n eLearning: getElearning(eLearningData),\r\n showCompletedValue: getShowCompleted(eLearningData)\r\n }\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tshowCompleted: (value) => dispatch(setShowCompleted(value))\r\n});\r\n\r\n\r\nElearningSchedules = connect(mapStoreToProps, mapDispatchToProps)(ElearningSchedules);\r\n\r\nElearningSchedules = withStyles(styles)(ElearningSchedules);\r\n\r\nexport { ElearningSchedules };\r\n","import {SET_SHOW_COMPLETED} from 'redux/reducers/elearning/action-types'\r\n\r\nconst setShowCompleted = (value) => ({\r\n\ttype: SET_SHOW_COMPLETED,\r\n\tvalue\r\n});\r\n\r\nexport {setShowCompleted}","\r\nconst styles = ({spacing, breakpoints, palette}) => ({\r\n\tscroller: {\r\n\t\theight: '100%',\r\n\t\toverflowY: 'scroll',\r\n\t\tpaddingBottom: 200,\r\n\t},\r\n\tlayout: {\r\n\t width: 'auto',\r\n\t display: 'block', // Fix IE11 issue.\r\n\t marginBottom: spacing.unit * 2,\r\n\t marginLeft: spacing.unit * 2,\r\n\t\tmarginRight: spacing.unit * 2,\r\n\t\tpaddingTop: spacing.unit * 2,\r\n\t [breakpoints.up(800 + spacing.unit * 3 * 2)]: {\r\n\t\t\twidth: 800,\r\n\t\t\tmarginLeft: 'auto',\r\n\t\t\tmarginRight: 'auto',\r\n\t\t},\r\n\t},\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t},\r\n\tform: {\r\n\t\tpadding: spacing.unit * 2,\r\n\t\tposition: 'relative',\r\n\t\tbackgroundColor: palette.background.light\r\n\t},\r\n\telearning: {\r\n\t\tmarginTop: spacing.unit * 4,\r\n\t\tpadding: spacing.unit * 2,\r\n\t\tposition: 'relative',\r\n\t\tbackgroundColor: palette.background.light\r\n\t},\t\r\n\tschedulesText: {\r\n\t\twidth: '100%',\r\n\t\tdisplay: 'flex',\r\n\t\tflexGrow: 1,\r\n\t\tpaddingLeft: 50,\r\n\t\tmarginBottom: spacing.unit\r\n\t},\r\n\tswitch:{\r\n\t\tdisplay: 'flex',\r\n\t\tjustifyContent: 'flex-end'\r\n\t},\r\n\tclockIcon: {\r\n\t\tfontSize: 38,\r\n\t\tleft: 15,\r\n\t\tmarginTop: 2,\r\n\t\tposition: 'absolute',\r\n\t\tdisplay: 'inline-block',\r\n\t\tcolor: palette.background.contrastText + \"!important\"\r\n\t},\r\n\tscheduleButton: {\r\n\t\twidth: 120,\r\n\t\tbackgroundColor: palette.secondary.main + \"!important\",\r\n\t\tcolor: palette.secondary.contrastText + \"!important\"\r\n\t},\r\n\tbuttonDisabled: {\r\n\t\tbackgroundColor: palette.secondary.light + \"!important\",\r\n\t\tcolor: palette.secondary.contrastText + \"!important\"\r\n\t}\r\n});\r\n\r\nexport {styles}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\n\r\n// material-ui\r\nimport { styles } from \"./styles\";\r\nimport Fade from \"@material-ui/core/Fade\";\r\nimport List from \"@material-ui/core/List\";\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport Divider from \"@material-ui/core/Divider\";\r\nimport Tooltip from \"@material-ui/core/Tooltip\";\r\nimport IconButton from \"@material-ui/core/IconButton\";\r\nimport AutoRenewIcon from \"@material-ui/icons/Autorenew\";\r\nimport AccessTimeIcon from \"@material-ui/icons/AccessTime\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\n// react\r\nimport { Clock } from \"./clock\";\r\nimport { ScheduleItem } from \"./item\";\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\n// redux (selectors)\r\nimport { getSchedulesData, getElearningData } from \"redux/reducers/selectors\";\r\nimport { getScheduleCount } from \"redux/reducers/schedules/selectors\";\r\nimport { getElearningCount } from \"redux/reducers/elearning/selectors\";\r\n\r\n// constants\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\nimport { ElearningSchedules } from \"components/pages/e_learning/elearning-schedules\";\r\n\r\nconst schedulesTitleMessageId = MESSAGE_IDS.SCHEDULE.TITLE;\r\n\r\n// SchedulesForm (not connected to store)\r\n// --------------------------------------------------------------------------\r\n\r\nclass SchedulesForm extends React.Component {\r\n render() {\r\n const { classes } = this.props;\r\n return (\r\n \r\n \r\n\t\t\t\t\t\r\n {this.Assessments}\r\n {this.Elearning}\r\n\t\t\t\t\t \r\n \r\n
\r\n );\r\n }\r\n\r\n get Assessments() {\r\n const { classes } = this.props;\r\n return (\r\n \r\n \r\n {this.SchedulesText}\r\n {this.RefreshButton}\r\n
\r\n {this.SchedulesList}\r\n \r\n );\r\n }\r\n\r\n get Elearning() {\r\n const { eLearningCount, classes } = this.props;\r\n if (eLearningCount === 0) return null;\r\n\r\n return (\r\n \r\n \r\n \r\n );\r\n }\r\n\r\n get SchedulesText() {\r\n const { classes } = this.props;\r\n return (\r\n \r\n
\r\n
\r\n {this.props.messages[schedulesTitleMessageId]}\r\n \r\n
\r\n
\r\n );\r\n }\r\n\r\n get SchedulesList() {\r\n const { classes } = this.props;\r\n const scheduleItems = [];\r\n for (var i = 0; i < this.props.scheduleCount; i++) {\r\n scheduleItems.push(\r\n \r\n \r\n \r\n \r\n );\r\n }\r\n return {scheduleItems}
;\r\n }\r\n\r\n get RefreshButton() {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nSchedulesForm.propTypes = {\r\n classes: PropTypes.object.isRequired,\r\n onRefresh: PropTypes.func.isRequired,\r\n scheduleCount: PropTypes.number.isRequired,\r\n messages: PropTypes.shape({\r\n [schedulesTitleMessageId]: PropTypes.string.isRequired,\r\n }).isRequired,\r\n};\r\n\r\n// SchedulesForm (connected to store/styles/messages)\r\n// --------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n scheduleCount: getScheduleCount(getSchedulesData(store)),\r\n eLearningCount: getElearningCount(getElearningData(store)),\r\n});\r\n\r\nSchedulesForm = connect(mapStoreToProps)(SchedulesForm);\r\nSchedulesForm = withMessages(SchedulesForm);\r\nSchedulesForm = withStyles(styles)(SchedulesForm);\r\n\r\nexport { SchedulesForm };\r\n","\r\n// npm\r\nimport React from 'react'\r\n\r\n// react\r\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\r\n\r\n\r\nconst SchedulesInitializationPage = ({children}) =>\r\n{\r\n\tconst appBarProps = {\r\n\t\ttitle: 'Fetching your schedules',\r\n\t\twelcomeMessage: true,\r\n\t\tloadingTitle: true,\r\n\t\tlogout: true,\r\n\t\tlogo: true\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t\t{children}\r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {SchedulesInitializationPage}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// redux (actions)\r\nimport {clearSchedules, clearElearning} from './actions'\r\n\r\n\r\n// SchedulesClearer (not connected to store)\r\n// -----------------------------------------------\r\n\r\nclass SchedulesClearer extends React.Component\r\n{\r\n\tcomponentDidMount() { this.props.clearSchedules();this.props.clearElearning(); }\r\n\trender() { return null; }\r\n}\r\n\r\nSchedulesClearer.propTypes = {\r\n\tclearSchedules: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// SchedulesClearer (connected to store)\r\n// --------------------------------------------------\r\n\r\nconst mapDispatchToProps = (dispatch, {onSuccess}) => ({\r\n\tclearSchedules: () => {\r\n\t\tdispatch(clearSchedules());\r\n\t\tonSuccess();\r\n\t},\r\n\tclearElearning: () => {\r\n\t\tdispatch(clearElearning());\r\n\t\tonSuccess();\r\n\t}\t\r\n})\r\n\r\nSchedulesClearer = connect(undefined, mapDispatchToProps)(SchedulesClearer);\r\n\r\n\r\n// EXPORT\r\n// -----------------------------------------------\r\nexport {SchedulesClearer}","\r\n// redux (action-types)\r\nimport {ADD_SCHEDULE, CLEAR_SCHEDULES} from 'redux/reducers/schedules/action-types'\r\nimport {ADD_ELEARNING, CLEAR_ELEARNING} from 'redux/reducers/elearning/action-types'\r\n\r\n\r\nconst addSchedule = (payload) => ({\r\n\ttype: ADD_SCHEDULE,\r\n\t...payload\r\n})\r\n\r\nconst addElearning = (payload) => ({\r\n\ttype: ADD_ELEARNING,\r\n\t...payload\r\n})\r\n\r\nconst clearSchedules = () => ({\r\n\ttype: CLEAR_SCHEDULES\r\n});\r\n\r\nconst clearElearning = () => ({\r\n\ttype: CLEAR_ELEARNING\r\n});\r\n\r\n\r\nexport {clearSchedules, addSchedule, addElearning, clearElearning}","import { check } from \"@xams-utils/check-types\";\r\n\r\nconst sortElearning = (eLearning) => {\r\n return eLearning.sort((a, b) => {\r\n if (a.status > b.status) return -1;\r\n else if (a.status < b.status) return 1;\r\n\r\n let dateA = null;\r\n let dateB = null;\r\n if (a.status === 2) {\r\n dateA = getTimeStamp(a.dateEnded);\r\n dateB = getTimeStamp(b.dateEnded);\r\n } else if (a.status === 1) {\r\n dateA = getTimeStamp(a.dateStarted);\r\n dateB = getTimeStamp(b.dateStarted);\r\n }\r\n if (dateA && dateB) {\r\n if (dateA < dateB) return -1;\r\n else if (dateA < dateB) return 1;\r\n }\r\n\r\n return a.attemptId < b.attemptId ? -1 : 1;\r\n });\r\n};\r\n\r\nconst getTimeStamp = (date) => {\r\n if (check.integer(date)) return date * 1000;\r\n const _date = new Date(date);\r\n return _date.getTime();\r\n};\r\n\r\nexport { sortElearning };\r\n","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\n// redux\r\nimport { addSchedule, addElearning } from \"./actions\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getUserGuid } from \"redux/reducers/session/user/selectors\";\r\nimport { getSessionData } from \"redux/reducers/selectors\";\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getProctoringMode } from \"redux/reducers/settings/client/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getUseLocalScormApi } from \"redux/reducers/settings/app/selectors\";\r\n\r\n// utils\r\nimport { assessmentApi } from \"libs/api/interface/api-assessment\";\r\nimport { eLearningApi } from \"libs/api/interface/api-e-learning\";\r\nimport { sortElearning } from \"./e-learning\";\r\n\r\n// SchedulesFetcher (not connected to store)\r\n// --------------------------------------------\r\n\r\nclass SchedulesFetcher extends React.Component {\r\n componentDidMount() {\r\n const onSuccess = (response) => {\r\n const parsedResponse = JSON.parse(response);\r\n\r\n const {assessments, courses} = parsedResponse;\r\n\r\n const uniqueAssessments = check.nonEmptyArray(assessments)\r\n ? assessments.filter((assessment, index, a) => {\r\n if (index === a.length - 1) {\r\n return true;\r\n }\r\n const b = a.slice(index + 1);\r\n const hasFutureExamGuid = check.nonEmptyArray(b)\r\n ? b.some(\r\n (c) => c.objectGUID === assessment.objectGUID\r\n )\r\n : false;\r\n\r\n return !hasFutureExamGuid;\r\n })\r\n : [];\r\n\r\n this.props.saveSchedules(uniqueAssessments || []);\r\n\r\n const sortedCourses = sortElearning(courses || []);\r\n this.props.saveElearning(sortedCourses);\r\n this.props.onSuccess();\r\n };\r\n assessmentApi\r\n .getExamScheduleWork(this.props.userGuid, this.props.proctoringMode)\r\n .then(onSuccess, this.props.onFail); \r\n }\r\n\r\n getElearning() {\r\n const onSuccess = (response) => {\r\n const parsedResponse = JSON.parse(response);\r\n\r\n const courses = sortElearning(parsedResponse.courses);\r\n this.props.saveElearning(courses);\r\n this.props.onSuccess();\r\n };\r\n\r\n const payload = {\r\n userGuid: this.props.userGuid,\r\n includeCompleted: true,\r\n };\r\n\r\n eLearningApi\r\n .getCourseAttempts(payload)\r\n .then(onSuccess, this.props.onFail);\r\n }\r\n\r\n render() {\r\n return null;\r\n }\r\n}\r\n\r\nSchedulesFetcher.propTypes = {\r\n userGuid: PropTypes.string.isRequired,\r\n saveSchedules: PropTypes.func.isRequired,\r\n onSuccess: PropTypes.func.isRequired,\r\n onFail: PropTypes.func.isRequired,\r\n};\r\n\r\n// SchedulesFetcher (connected to store)\r\n// --------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n const settingsData = getSettingsData(store);\r\n\r\n return {\r\n userGuid: getUserGuid(getUserSessionData(getSessionData(store))),\r\n proctoringMode: getProctoringMode(\r\n getClientSettingsData(settingsData)\r\n ),\r\n };\r\n};\r\n\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n saveSchedules: (response) => {\r\n response.forEach((item) => {\r\n dispatch(addSchedule(item));\r\n });\r\n },\r\n saveElearning: (response) => {\r\n response.forEach((item) => {\r\n dispatch(addElearning(item));\r\n });\r\n },\r\n});\r\n\r\nSchedulesFetcher = connect(\r\n mapStoreToProps,\r\n mapDispatchToProps\r\n)(SchedulesFetcher);\r\n\r\nexport { SchedulesFetcher };\r\n","\nimport {Machine} from 'xstate'\nimport {XStateConfig} from './xstate-config'\n\n\n// #########################################\n// STATE & EVENT NAMES\n// #########################################\n\nconst STATES = {\n\tINITIALIZING_SCHEDULES: 'initializing_schedules',\n\tDISPLAYING_SCHEDULES: 'displaying_schedules',\n\tINITIALIZATION_ERROR: 'schedule_initialization_error'\n}\n\nconst INITIALIZING_STATES = {\n\tCLEARING_SCHEDULES: 'clearing_old_schedules',\n\tFETCHING_SCHEDULES: 'fetching_updated_schedules'\n}\n\nconst EVENTS = {\n\tSUCCESS: 'success',\n\tREFRESH: 'refresh',\n\tERROR: 'error'\n}\n\n\n// #########################################\n// INITIALIZING STATES\n// #########################################\n\nconst clearingSchedules = new XStateConfig();\nclearingSchedules.addTransition(EVENTS.SUCCESS, INITIALIZING_STATES.FETCHING_SCHEDULES);\n\nconst fetchingSchedules = new XStateConfig();\n\n\n// #########################################\n// SCHEDULES STATES\n// #########################################\n\nconst initializingSchedules = new XStateConfig();\ninitializingSchedules.initialState = INITIALIZING_STATES.CLEARING_SCHEDULES;\ninitializingSchedules.addState(INITIALIZING_STATES.CLEARING_SCHEDULES, clearingSchedules);\ninitializingSchedules.addState(INITIALIZING_STATES.FETCHING_SCHEDULES, fetchingSchedules);\ninitializingSchedules.addTransition(EVENTS.SUCCESS, STATES.DISPLAYING_SCHEDULES);\ninitializingSchedules.addTransition(EVENTS.ERROR, STATES.INITIALIZATION_ERROR);\n\nconst displayingSchedules = new XStateConfig();\ndisplayingSchedules.addTransition(EVENTS.REFRESH, STATES.INITIALIZING_SCHEDULES);\n\nconst initializationError = new XStateConfig();\n\n\n// #########################################\n// SCHEDULES MACHINE\n// #########################################\n\nconst _schedules = new XStateConfig();\n_schedules.initialState = STATES.INITIALIZING_SCHEDULES;\n_schedules.addState(STATES.INITIALIZING_SCHEDULES, initializingSchedules);\n_schedules.addState(STATES.DISPLAYING_SCHEDULES, displayingSchedules);\n_schedules.addState(STATES.INITIALIZATION_ERROR, initializationError);\n\nconst machine = Machine(_schedules.toObject());\nmachine.id = \"Schedules Machine\";\n\n\n// #########################################\n// EXPORT\n// #########################################\n\nconst schedules = {\n\tmachine,\n\tEVENTS: {...EVENTS},\n\tSTATES: {...STATES},\n\tINITIALIZING_STATES: {...INITIALIZING_STATES}\n}\n\nexport {schedules}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {SchedulesInitializationPage} from './page'\r\nimport {SchedulesClearer} from './schedules-clearer'\r\nimport {SchedulesFetcher} from './schedules-fetcher'\r\n\r\n// machines\r\nimport {schedules} from 'machines/schedules'\r\n\r\n\r\nconst {EVENTS, INITIALIZING_STATES:STATES} = schedules;\r\n\r\n\r\nconst InitializingSchedulesView = () =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{{\r\n\t\t\t\t\t[STATES.CLEARING_SCHEDULES]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{({onSuccess}) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.FETCHING_SCHEDULES]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{(props) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t)\r\n\t\t\t\t}}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {InitializingSchedulesView}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateMachine, StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {SchedulesForm} from './form'\r\nimport {InitializingSchedulesView} from './initializing_schedules/view'\r\nimport {SetNetworkError} from 'components/pages/network_error/set-network-error'\r\n\r\n// machines\r\nimport {schedules} from 'machines/schedules'\r\n\r\n\r\nconst {STATES, EVENTS} = schedules;\r\n\r\n\r\nconst SchedulesPageMachine = () =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{{\r\n\t\t\t\t\t[STATES.INITIALIZING_SCHEDULES]: () => (\r\n\t\t\t\t\t\tInitializingSchedulesView()\r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.DISPLAYING_SCHEDULES]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{({onRefresh}) => }\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.INITIALIZATION_ERROR]: () => (\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t)\r\n\t\t\t\t}}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {SchedulesPageMachine}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// react\r\nimport {SchedulesPageMachine} from './machine'\r\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n// redux (selectors)\r\nimport {getSettingsData} from 'redux/reducers/selectors'\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getProctoringMode} from 'redux/reducers/settings/client/selectors'\r\n\r\n\r\nlet SchedulesPage = ({messages, proctoringMode}) =>\r\n{\r\n\tconst appBarProps = {\r\n\t\ttitle: messages[MESSAGE_IDS.SCHEDULE.APP_BAR_TITLE],\r\n\t\twelcomeMessage: true,\r\n\t\tlogout: proctoringMode == null,\r\n\t\tlogo: true\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n// SchedulesPage.propTypes = {\r\n// \tproctoringMode: PropTypes.number.isRequired\r\n// }\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\treturn {\r\n\t\tproctoringMode: getProctoringMode(getClientSettingsData(getSettingsData(store)))\r\n\t}\r\n}\r\n\r\nSchedulesPage = withMessages(SchedulesPage);\r\nSchedulesPage = connect(mapStoreToProps)(SchedulesPage);\r\n\r\nexport {SchedulesPage}","import React from \"react\";\r\n\r\nimport moment from \"moment\";\r\nimport isEqual from \"lodash/isEqual\";\r\n\r\nimport classNames from \"classnames\";\r\nimport PropTypes from \"prop-types\";\r\nimport { withStyles } from \"@material-ui/core/styles\";\r\nimport Table from \"@material-ui/core/Table\";\r\nimport TableBody from \"@material-ui/core/TableBody\";\r\nimport TableCell from \"@material-ui/core/TableCell\";\r\nimport TableHead from \"@material-ui/core/TableHead\";\r\nimport TablePagination from \"@material-ui/core/TablePagination\";\r\nimport TableRow from \"@material-ui/core/TableRow\";\r\nimport TableSortLabel from \"@material-ui/core/TableSortLabel\";\r\nimport Toolbar from \"@material-ui/core/Toolbar\";\r\nimport Typography from \"@material-ui/core/Typography\";\r\nimport Paper from \"@material-ui/core/Paper\";\r\nimport Checkbox from \"@material-ui/core/Checkbox\";\r\nimport IconButton from \"@material-ui/core/IconButton\";\r\nimport Tooltip from \"@material-ui/core/Tooltip\";\r\n\r\nimport { lighten } from \"@material-ui/core/styles/colorManipulator\";\r\n\r\nimport { ACTIVITIES } from \"libs/activity_logger/activity-logger\";\r\nimport { decryptValue, getActivityTypeName } from \"libs/activity_logger/activity-logger-helper\";\r\n\r\nlet counter = 0;\r\nfunction createData(name, calories, fat, carbs, protein) {\r\n counter += 1;\r\n return { id: counter, name, calories, fat, carbs, protein };\r\n}\r\n\r\nconst displayDate = (dateString) => {\r\n if (!dateString) return \"\";\r\n return new moment(dateString).format(\"Do MMMM YY HH:mm:ss:SSS\");\r\n};\r\n\r\nconst displayActivityType = (activity) => {\r\n if (activity === ACTIVITIES.LOCAL_ANSWER) return \"LOCAL ANSWER\";\r\n else if (activity === ACTIVITIES.SERVER_ANSWER) return \"SERVER ANSWER\";\r\n else if (activity === ACTIVITIES.MOVE_QUESTION) return \"MOVE QUESTION\";\r\n else if (activity === ACTIVITIES.MOVE_NEXT_QUESTION)\r\n return \"MOVE NEXT QUESTION\";\r\n else if (activity === ACTIVITIES.MOVE_PREVIOUS_QUESTION)\r\n return \"MOVE PREVIOUS QUESTION\";\r\n else if (activity === ACTIVITIES.WORKINGS) return \"WORKINGS\";\r\n else if (activity === ACTIVITIES.START_EXAM) return \"START EXAM\";\r\n else if (activity === ACTIVITIES.RESUME_EXAM) return \"RESUME EXAM\";\r\n else if (activity === ACTIVITIES.SET_FORM_RUN_GUID)\r\n return \"SET RUN FORM GUID\";\r\n else if (activity === ACTIVITIES.TIMED_OUT) return \"TIMED OUT\";\r\n else if (activity === ACTIVITIES.FINISH_EXAM) return \"FINISH EXAM\";\r\n else if (activity === ACTIVITIES.MOVE_SECTION) return \"MOVE SECTION\";\r\n else if (activity === ACTIVITIES.OFFLINE) return \"OFFLINE\";\r\n else if (activity === ACTIVITIES.ONLINE) return \"ONLINE\";\r\n else if (activity === ACTIVITIES.UPLOAD_ONLINE) return \"ONLINE UPLOAD\";\r\n else if (activity === ACTIVITIES.UPLOAD_OFFLINE) return \"OFF UPLOAD\";\r\n else if (activity === ACTIVITIES.DELETE_UPLOAD_ONLINE) return \"DELETE ONLINE UPLOAD\";\r\n else if (activity === ACTIVITIES.DELETE_UPLOAD_OFFLINE) return \"DELETE OFF UPLOAD\"; \r\n else if (activity === ACTIVITIES.CONFIRM_POPUP) return \"USER FINISH\";\r\n else if (activity === ACTIVITIES.DISPLAY_POPUP) return \"DISPLAY POPUP\";\r\n else if (activity === ACTIVITIES.POPUP_ERROR) return \"DISPLAY POPUP\"; \r\n\r\n return activity;\r\n};\r\n\r\nconst displayActivityValue = (value) => {\r\n //return value ? window.atob(value) : \"\";\r\n return decryptValue(value);\r\n};\r\n\r\nconst displayCellData = (column, rowData) => {\r\n const data = rowData[column.id];\r\n\r\n if (column.date) return displayDate(data);\r\n else if (column.activityType){\r\n //return displayActivityType(data);\r\n return getActivityTypeName(ACTIVITIES, data);\r\n }\r\n else if (column.activityValue) return displayActivityValue(data);\r\n\r\n return data;\r\n};\r\n\r\nfunction desc(a, b, orderBy) {\r\n if (b[orderBy] < a[orderBy]) {\r\n return -1;\r\n }\r\n if (b[orderBy] > a[orderBy]) {\r\n return 1;\r\n }\r\n return 0;\r\n}\r\n\r\nfunction stableSort(array, cmp) {\r\n const stabilizedThis = array.map((el, index) => [el, index]);\r\n stabilizedThis.sort((a, b) => {\r\n const order = cmp(a[0], b[0]);\r\n if (order !== 0) return order;\r\n return a[1] - b[1];\r\n });\r\n return stabilizedThis.map((el) => el[0]);\r\n}\r\n\r\nfunction getSorting(order, orderBy) {\r\n return order === \"desc\"\r\n ? (a, b) => desc(a, b, orderBy)\r\n : (a, b) => -desc(a, b, orderBy);\r\n}\r\n\r\nclass EnhancedTableHead extends React.Component {\r\n createSortHandler = (property) => (event) => {\r\n this.props.onRequestSort(event, property);\r\n };\r\n\r\n render() {\r\n const {\r\n onSelectAllClick,\r\n order,\r\n orderBy,\r\n numSelected,\r\n rowCount,\r\n rows,\r\n displayCheckBox,\r\n } = this.props;\r\n\r\n return (\r\n \r\n \r\n {displayCheckBox ? (\r\n \r\n 0 && numSelected < rowCount\r\n }\r\n checked={numSelected === rowCount}\r\n onChange={onSelectAllClick}\r\n />\r\n \r\n ) : (\r\n \r\n )}\r\n {rows.map(\r\n (row) => (\r\n \r\n \r\n \r\n {row.label}\r\n \r\n \r\n \r\n ),\r\n this\r\n )}\r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nEnhancedTableHead.propTypes = {\r\n numSelected: PropTypes.number.isRequired,\r\n onRequestSort: PropTypes.func.isRequired,\r\n onSelectAllClick: PropTypes.func.isRequired,\r\n order: PropTypes.string.isRequired,\r\n orderBy: PropTypes.string.isRequired,\r\n rowCount: PropTypes.number.isRequired,\r\n};\r\n\r\nconst toolbarStyles = (theme) => ({\r\n root: {\r\n paddingRight: theme.spacing.unit,\r\n },\r\n highlight:\r\n theme.palette.type === \"light\"\r\n ? {\r\n color: theme.palette.secondary.main,\r\n backgroundColor: lighten(theme.palette.secondary.light, 0.85),\r\n }\r\n : {\r\n color: theme.palette.text.primary,\r\n backgroundColor: theme.palette.secondary.dark,\r\n },\r\n spacer: {\r\n flex: \"1 1 100%\",\r\n },\r\n actions: {\r\n color: theme.palette.text.secondary,\r\n },\r\n title: {\r\n flex: \"0 0 auto\",\r\n },\r\n icons: {\r\n display: \"flex\",\r\n },\r\n});\r\n\r\nlet EnhancedTableToolbar = (props) => {\r\n const { numSelected, classes, title, icons } = props;\r\n\r\n return (\r\n \r\n \r\n \r\n {title}\r\n \r\n
\r\n
\r\n \r\n
\r\n {icons.map((icon, index) => {\r\n const { disabled, name, onSelect } = icon;\r\n return (\r\n \r\n \r\n {icon.icon}\r\n \r\n \r\n );\r\n })}\r\n
\r\n
\r\n \r\n );\r\n};\r\n\r\nEnhancedTableToolbar.propTypes = {\r\n classes: PropTypes.object.isRequired,\r\n numSelected: PropTypes.number.isRequired,\r\n};\r\n\r\nEnhancedTableToolbar = withStyles(toolbarStyles)(EnhancedTableToolbar);\r\n\r\nconst styles = (theme) => ({\r\n root: {\r\n width: \"100%\",\r\n marginTop: theme.spacing.unit * 3,\r\n },\r\n table: {\r\n minWidth: 1020,\r\n },\r\n tableWrapper: {\r\n overflowX: \"auto\",\r\n },\r\n});\r\n\r\nclass EnhancedTable extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n const { data } = props;\r\n\r\n this.state = {\r\n order: \"asc\",\r\n orderBy: \"calories\",\r\n selected: [],\r\n data,\r\n page: 0,\r\n rowsPerPage: props.rowsPerPage || 5,\r\n };\r\n }\r\n\r\n componentDidUpdate(prevProps, prevState) {\r\n const { data: prevData, selected: prevSelected } = prevState;\r\n const { selected } = this.state;\r\n const { data, onSelectChange } = this.props;\r\n\r\n if (prevData.length !== data.length) this.setState({ data });\r\n if (!isEqual(selected, prevSelected) && onSelectChange)\r\n onSelectChange(selected);\r\n }\r\n\r\n handleRequestSort = (event, property) => {\r\n const orderBy = property;\r\n let order = \"desc\";\r\n\r\n if (this.state.orderBy === property && this.state.order === \"desc\") {\r\n order = \"asc\";\r\n }\r\n\r\n this.setState({ order, orderBy });\r\n };\r\n\r\n handleSelectAllClick = (event) => {\r\n if (event.target.checked) {\r\n this.setState((state) => ({\r\n selected: state.data.map((n) => n.id),\r\n }));\r\n return;\r\n }\r\n this.setState({ selected: [] });\r\n };\r\n\r\n handleClick = (event, id) => {\r\n const { selected } = this.state;\r\n const selectedIndex = selected.indexOf(id);\r\n let newSelected = [];\r\n\r\n if (selectedIndex === -1) {\r\n newSelected = newSelected.concat(selected, id);\r\n } else if (selectedIndex === 0) {\r\n newSelected = newSelected.concat(selected.slice(1));\r\n } else if (selectedIndex === selected.length - 1) {\r\n newSelected = newSelected.concat(selected.slice(0, -1));\r\n } else if (selectedIndex > 0) {\r\n newSelected = newSelected.concat(\r\n selected.slice(0, selectedIndex),\r\n selected.slice(selectedIndex + 1)\r\n );\r\n }\r\n\r\n this.setState({ selected: newSelected });\r\n };\r\n\r\n handleSelect = (event, activity) => {\r\n const { onSelect } = this.props;\r\n if (onSelect) onSelect(activity);\r\n };\r\n\r\n handleChangePage = (event, page) => {\r\n this.setState({ page });\r\n };\r\n\r\n handleChangeRowsPerPage = (event) => {\r\n this.setState({ rowsPerPage: event.target.value });\r\n };\r\n\r\n isSelected = (id) => this.state.selected.indexOf(id) !== -1;\r\n\r\n render() {\r\n const { classes, rows, title, checkBox, icons } = this.props;\r\n const {\r\n data,\r\n order,\r\n orderBy,\r\n selected,\r\n rowsPerPage,\r\n page,\r\n } = this.state;\r\n const emptyRows =\r\n rowsPerPage -\r\n Math.min(rowsPerPage, data.length - page * rowsPerPage);\r\n\r\n return (\r\n \r\n \r\n \r\n
\r\n \r\n \r\n {stableSort(data, getSorting(order, orderBy))\r\n .slice(\r\n page * rowsPerPage,\r\n page * rowsPerPage + rowsPerPage\r\n )\r\n .map((n) => {\r\n const isSelected = this.isSelected(n.id);\r\n return (\r\n \r\n {checkBox ? (\r\n \r\n \r\n this.handleClick(\r\n event,\r\n n.id\r\n )\r\n }\r\n />\r\n \r\n ) : (\r\n \r\n )}\r\n {rows.map((row, index) => {\r\n return (\r\n \r\n this.handleSelect(\r\n event,\r\n n\r\n )\r\n }\r\n >\r\n {displayCellData(\r\n row,\r\n n\r\n )}\r\n \r\n );\r\n })}\r\n \r\n );\r\n })}\r\n {emptyRows > 0 && (\r\n \r\n \r\n \r\n )}\r\n \r\n
\r\n
\r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nEnhancedTable.propTypes = {\r\n classes: PropTypes.object.isRequired,\r\n};\r\n\r\nEnhancedTable = withStyles(styles)(EnhancedTable);\r\n\r\nexport { EnhancedTable };\r\n","import { assessmentApi } from \"libs/api/interface/api-assessment\";\r\n\r\nimport { activityLoggerSent } from \"./activity-logger-sent\";\r\nimport { dbStorageApi, KEYS } from \"libs/browser_storage/db-storage-api\";\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\nconst resetActivityLogger = () => {\r\n return new Promise((resolve) => {\r\n dbStorageApi.delete(KEYS.STUDENT_ACTIVITY).then(() => {\r\n dbStorageApi.delete(KEYS.SENT_EXAMS).then(() => {\r\n dbStorageApi.initializeDB(0).then(() => {\r\n dbStorageApi.initializeDB(1).then(() => {\r\n activityLoggerSent.clear();\r\n resolve();\r\n });\r\n });\r\n });\r\n });\r\n });\r\n};\r\n\r\nconst deleteFormRunGuids = (formRunGuids) => {\r\n return new Promise((resolve) => {\r\n if (!check.nonEmptyArray(formRunGuids)) {\r\n resolve();\r\n } else {\r\n deleteKeyFormRunGuids(formRunGuids, KEYS.STUDENT_ACTIVITY).then(\r\n () => {\r\n deleteKeyFormRunGuids(formRunGuids, KEYS.SENT_EXAMS).then(\r\n () => {\r\n activityLoggerSent.remove(formRunGuids).then(() => {\r\n resolve();\r\n });\r\n }\r\n );\r\n }\r\n );\r\n }\r\n });\r\n};\r\n\r\nconst deleteKeyFormRunGuids = (formRunGuids, key) => {\r\n return new Promise((resolve) => {\r\n dbStorageApi.retrieveAll(key, true).then((records) => {\r\n const deletedRecords = records\r\n .filter(\r\n (record) => formRunGuids.indexOf(record.formRunGuid) !== -1\r\n )\r\n .map((record) => ({\r\n _id: record.id,\r\n _rev: record._rev,\r\n _deleted: true,\r\n }));\r\n\r\n dbStorageApi.remove(key, deletedRecords).then(() => {\r\n resolve();\r\n });\r\n });\r\n });\r\n};\r\n\r\nconst uploadFormRunGuids = (formRunGuids) => {\r\n return new Promise((resolve) => {\r\n const foundFormRunGuids = {};\r\n dbStorageApi\r\n .retrieveAll(KEYS.STUDENT_ACTIVITY, true)\r\n .then((records) => {\r\n records.forEach((record) => {\r\n const { formRunGuid } = record;\r\n if (formRunGuids.indexOf(formRunGuid) !== -1) {\r\n if (!foundFormRunGuids[formRunGuid])\r\n foundFormRunGuids[formRunGuid] = {\r\n formRunGuid,\r\n activities: [],\r\n };\r\n foundFormRunGuids[\r\n formRunGuid\r\n ].activities = foundFormRunGuids[\r\n formRunGuid\r\n ].activities.concat([record]);\r\n }\r\n });\r\n\r\n const _foundFormRunGuids = [];\r\n Object.keys(foundFormRunGuids).forEach(\r\n (foundFormRunGuid, index) => {\r\n _foundFormRunGuids[index] = foundFormRunGuids[foundFormRunGuid];\r\n }\r\n );\r\n if (check.nonEmptyArray(_foundFormRunGuids)){\r\n uploadFormRunGuid(_foundFormRunGuids, 0, resolve);\r\n }\r\n else{\r\n resolve();\r\n }\r\n });\r\n });\r\n};\r\n\r\nconst uploadFormRunGuid = (formRunGuids, currentFormRunGuid, resolve) => {\r\n const { formRunGuid, activities } = formRunGuids[currentFormRunGuid];\r\n const payload = {\r\n formRunGuid,\r\n activities: activities.map((activity) => {\r\n const { id, type, value } = activity;\r\n return {\r\n id,\r\n type,\r\n value,\r\n };\r\n }),\r\n };\r\n\r\n const onComplete = () => {\r\n if (currentFormRunGuid === formRunGuids.length - 1) {\r\n resolve();\r\n } else {\r\n uploadFormRunGuid(formRunGuids, currentFormRunGuid + 1, resolve);\r\n }\r\n };\r\n\r\n const onSuccess = () => {\r\n return activityLoggerSent.reSend(formRunGuid).then(() => {\r\n onComplete();\r\n });\r\n };\r\n\r\n assessmentApi.saveActivityLog(payload).then(onSuccess, onComplete);\r\n};\r\n\r\nexport { resetActivityLogger, deleteFormRunGuids, uploadFormRunGuids };\r\n","import React, { Component } from \"react\";\r\nimport { withRouter } from \"react-router-dom\";\r\n\r\nimport { LoadingSpinner } from \"components/presentation/loading-spinner\";\r\n\r\nimport { EnhancedTable } from \"./activity-log-table\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport ArrowUpwardIcon from \"@material-ui/icons/ArrowUpward\";\r\nimport DeleteIcon from \"@material-ui/icons/Delete\";\r\nimport ClearIcon from \"@material-ui/icons/Clear\";\r\nimport CloudUploadIcon from '@material-ui/icons/CloudUpload';\r\n\r\nimport { activityLogger } from \"libs/activity_logger/activity-logger\";\r\nimport {resetActivityLogger, deleteFormRunGuids, uploadFormRunGuids} from \"libs/activity_logger/activity-logger-reset\";\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\n\r\n\r\nconst styles = ({ spacing, breakpoints, palette }) => ({\r\n root: {\r\n margin: spacing.unit * 3,\r\n overflowX: \"auto\",\r\n },\r\n table: {\r\n minWidth: 700,\r\n },\r\n layout: {\r\n marginTop: spacing.unit * 3,\r\n width: \"auto\",\r\n display: \"block\", // Fix IE11 issue.\r\n marginLeft: spacing.unit * 2,\r\n marginRight: spacing.unit * 2,\r\n paddingTop: spacing.unit * 2,\r\n [breakpoints.up(800 + spacing.unit * 3 * 2)]: {\r\n width: 800,\r\n marginLeft: \"auto\",\r\n marginRight: \"auto\",\r\n },\r\n },\r\n});\r\n\r\nclass ActivityLogsPage extends Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n this.timer = null;\r\n this.state = { logs: [], selected: [], busy: false };\r\n\r\n this.handleSelectChange = this.handleSelectChange.bind(this);\r\n this.handleDelete = this.handleDelete.bind(this);\r\n this.handleReset = this.handleReset.bind(this);\r\n this.handleUpload = this.handleUpload.bind(this);\r\n }\r\n\r\n componentDidMount() {\r\n this.getData();\r\n }\r\n\r\n componentWillUnmount() {\r\n clearTimeout(this.timer);\r\n }\r\n\r\n getData() {\r\n if (activityLogger.initialised) {\r\n activityLogger.getLogs().then((logs) => {\r\n this.setState({ logs });\r\n });\r\n } else {\r\n this.timer = setTimeout(() => {\r\n this.getData();\r\n }, 500);\r\n }\r\n }\r\n\r\n handleSelectChange(selected){\r\n this.setState({selected});\r\n }\r\n\r\n handleDelete(){\r\n const {selected, logs} = this.state;\r\n if (logs.length === selected.length){\r\n this.handleReset();\r\n }\r\n else{\r\n const formRunGuids = selected.map(item=>logs[item-1].formRunGuid);\r\n this.setState({busy: true, logs:[]}, ()=>{\r\n deleteFormRunGuids(formRunGuids).then(()=>{\r\n this.setState({busy: false}, ()=>{\r\n this.getData();\r\n });\r\n })\r\n });\r\n }\r\n }\r\n\r\n handleUpload(){\r\n const {selected, logs} = this.state;\r\n const formRunGuids = selected.map(item=>logs[item-1].formRunGuid);\r\n this.setState({busy: true, logs:[]}, ()=>{\r\n uploadFormRunGuids(formRunGuids).then(()=>{\r\n this.setState({busy: false}, ()=>{\r\n this.getData();\r\n });\r\n })\r\n }); \r\n }\r\n\r\n handleReset(){\r\n this.setState({busy: true, logs:[]}, ()=>{\r\n resetActivityLogger().then(()=>{\r\n this.setState({busy: false}, ()=>{\r\n this.getData();\r\n });\r\n })\r\n });\r\n }\r\n\r\n reDisplay(){\r\n\r\n }\r\n\r\n render() {\r\n const appBarProps = {\r\n title: \"Activity Logs\",\r\n welcomeMessage: true,\r\n logout: true,\r\n logo: true,\r\n };\r\n\r\n return (\r\n \r\n \r\n {this.displayLogs()}\r\n \r\n );\r\n }\r\n\r\n displayLogs() {\r\n const { logs, selected, busy } = this.state;\r\n\r\n if (busy) {\r\n return ;\r\n }\r\n\r\n const { classes } = this.props;\r\n const rows = [\r\n {\r\n id: \"formRunGuid\",\r\n numeric: false,\r\n disablePadding: true,\r\n label: \"Form Run Guid\",\r\n },\r\n { id: \"date\", date: true, disablePadding: true, label: \"Date\" },\r\n { id: \"sent\", date: true, disablePadding: true, label: \"Sent\" },\r\n ];\r\n\r\n const icons = [\r\n {\r\n name: \"Schedules\",\r\n icon: ,\r\n onSelect: () => {\r\n this.movePage(\"schedules\");\r\n },\r\n },\r\n {\r\n name: \"Reset\",\r\n icon: ,\r\n onSelect: this.handleReset,\r\n },\r\n {\r\n name: \"Delete\",\r\n icon: ,\r\n disabled: selected.length===0,\r\n onSelect: this.handleDelete,\r\n },\r\n {\r\n name: \"Upload\",\r\n icon: ,\r\n disabled: selected.length===0,\r\n onSelect: this.handleUpload,\r\n }, \r\n ];\r\n\r\n const props = {\r\n title: \"Activity Logs\",\r\n rows,\r\n data: logs,\r\n checkBox: true,\r\n rowsPerPage: 10,\r\n onSelect: ({ formRunGuid }) => {\r\n const { history } = this.props;\r\n history.replace(`/log/${formRunGuid}`);\r\n },\r\n onSelectChange: this.handleSelectChange,\r\n icons,\r\n };\r\n\r\n return (\r\n \r\n \r\n
\r\n );\r\n }\r\n\r\n movePage(page) {\r\n const { history } = this.props;\r\n history.replace(`/${page}`);\r\n }\r\n}\r\n\r\nActivityLogsPage = withStyles(styles)(ActivityLogsPage);\r\nActivityLogsPage = withRouter(ActivityLogsPage);\r\n\r\nexport { ActivityLogsPage };\r\n","import React, { Component } from \"react\";\r\nimport {withRouter} from 'react-router-dom'\r\n\r\nimport moment from \"moment\";\r\n\r\nimport {EnhancedTable} from './activity-log-table'\r\nimport ArrowUpwardIcon from \"@material-ui/icons/ArrowUpward\";\r\nimport ArrowBackIcon from \"@material-ui/icons/ArrowBack\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nimport { activityLogger } from \"libs/activity_logger/activity-logger\";\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\n\r\nconst styles = ({ spacing, breakpoints, palette }) => ({\r\n root: {\r\n margin: spacing.unit * 3,\r\n overflowX: 'auto',\r\n }\r\n});\r\n\r\nclass ActivityLogPage extends Component {\r\n constructor(props) {\r\n super(props);\r\n\r\n const {\r\n match: { params },\r\n } = props;\r\n\r\n this.formRunGuid = params.logId;\r\n this.timer=null;\r\n\r\n this.state = { activities: []};\r\n }\r\n\r\n componentDidMount() {\r\n this.getData();\r\n }\r\n\r\n componentWillUnmount() {\r\n clearTimeout(this.timer);\r\n }\r\n\r\n getData() {\r\n if (activityLogger.initialised) {\r\n activityLogger.get(this.formRunGuid).then((activities) => {\r\n this.setState({ activities });\r\n });\r\n } else {\r\n this.timer = setTimeout(() => {\r\n this.getData();\r\n }, 500);\r\n }\r\n }\r\n\r\n render() {\r\n const appBarProps = {\r\n title: `Activity Log - ${this.formRunGuid}`,\r\n welcomeMessage: true,\r\n logout: true,\r\n logo: true,\r\n };\r\n\r\n return (\r\n \r\n \r\n {this.displayActivities()}\r\n \r\n );\r\n }\r\n\r\n displayActivities() {\r\n const { activities } = this.state;\r\n const { classes } = this.props;\r\n\r\n const rows = [\r\n { id: 'id', date: true, disablePadding: false, label: 'Date' },\r\n { id: 'type', activityType: true, disablePadding: true, label: 'Activity' },\r\n { id: 'value', activityValue: true, disablePadding: true, label: 'Value' }\r\n ];\r\n\r\n const icons = [\r\n {name:'Activity Logs', icon: , onSelect:()=>{this.movePage('logs')}}, \r\n {name:'Schedules', icon: , onSelect:()=>{this.movePage('schedules')}},\r\n ]\r\n\r\n const props = {\r\n title: 'Activity Logs',\r\n rowsPerPage: 10,\r\n checkBox: false,\r\n rows,\r\n data: activities,\r\n icons\r\n };\r\n\r\n return
;\r\n }\r\n\r\n movePage(page){\r\n const { history } = this.props;\r\n history.replace(`/${page}`); \r\n }\r\n}\r\n\r\nActivityLogPage = withStyles(styles)(ActivityLogPage);\r\nActivityLogPage = withRouter(ActivityLogPage);\r\n\r\nexport { ActivityLogPage };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport {withRouter} from 'react-router-dom'\r\n\r\n// redux (actions)\r\nimport {getRemoveCurrentSessionActions} from 'components/actions'\r\n\r\n// redux (selectors)\r\nimport {getUserGuid} from 'redux/reducers/session/user/selectors'\r\nimport {getUrlToken} from 'redux/reducers/session/client/selectors'\r\nimport {getAppSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getSessionData, getSettingsData} from 'redux/reducers/selectors'\r\nimport {isInactivityTimerActive} from 'redux/reducers/settings/app/selectors'\r\nimport {getClientSessionData, getUserSessionData} from 'redux/reducers/session/selectors'\r\n\r\n// utils\r\nimport {inactivityManager} from 'utils/inactivity-manager'\r\nimport {authenticationApi} from 'libs/api/interface/api-authentication'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\n// InactivityDetector (not connected to store/router)\r\n// -----------------------------------------------------------------------\r\n\r\nclass InactivityDetector extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.initializeBoundMethods();\t\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.handleInactivity = () => {\r\n\t\t\tconst {messages} = this.props;\r\n\t\t\twindow.alert(messages[MESSAGE_IDS.SESSION.INACTIVITY]);\r\n\t\t\tthis.props.logOut();\r\n\t\t}\r\n\t}\r\n\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tif (this.props.active) {\r\n\t\t\tinactivityManager.subscribe(this.handleInactivity);\r\n\t\t}\r\n\t}\r\n\r\n\tcomponentDidUpdate(previousProps)\r\n\t{\r\n\t\tif (previousProps.active !== this.props.active) {\r\n\t\t\tif (this.props.active) {\r\n\t\t\t\tinactivityManager.subscribe(this.handleInactivity);\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tinactivityManager.unsubscribe();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn this.props.children || null;\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tif (this.props.active) {\r\n\t\t\tinactivityManager.unsubscribe();\r\n\t\t}\r\n\t}\r\n}\r\n\r\nInactivityDetector.propTypes = {\r\n\tchildren: PropTypes.node,\r\n\tactive: PropTypes.bool.isRequired,\r\n\tlogOut: PropTypes.func.isRequired,\r\n\tmessages: PropTypes.shape({\r\n\t\t[MESSAGE_IDS.SESSION.INACTIVITY]: PropTypes.string.isRequired\r\n\t}).isRequired\r\n}\r\n\r\n\r\n// InactivityDetector (connected to store)\r\n// -----------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({store});\r\nconst mapDispatchToProps = (dispatch) => ({dispatch});\r\n\r\n\r\nconst isActive = (store) =>\r\n{\r\n\tconst settingsData = getSettingsData(store);\r\n\tconst appSettingsData = getAppSettingsData(settingsData);\r\n\treturn isInactivityTimerActive(appSettingsData);\r\n}\r\n\r\n// const isExamPreview = (store) => \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// decided to keep inactivity timer active during exam previews (9/12/19)\r\n// {\r\n// \tconst examData = getExamData(store);\r\n// \tconst examGuid = getGuid(examData);\r\n// \tif (!examGuid) { return false; }\r\n\r\n// \tconst schedulesData = getSchedulesData(store);\r\n// \tconst scheduleData = getScheduleDataByExamGuid(schedulesData)(examGuid);\r\n// \tif (!scheduleData) { return false; }\r\n\r\n// \treturn !!getVersion(scheduleData);\r\n// }\r\n\r\nconst createLogoutFunction = (store, dispatch, history) => () =>\r\n{\r\n\tconst sessionData = getSessionData(store);\r\n\tconst urlToken = getUrlToken(getClientSessionData(sessionData));\r\n\tconst userGuid = getUserGuid(getUserSessionData(sessionData));\r\n\r\n\tauthenticationApi.abandonUserSession(userGuid, \"Inactivity\");\r\n\thistory.push(`/login?${urlToken}`);\r\n\tgetRemoveCurrentSessionActions().forEach(action => dispatch(action));\r\n}\r\n\r\nconst mergeProps = ({store}, {dispatch}, {history, children, messages}) =>\r\n{\r\n\tconst active = isActive(store);\r\n\tconst logOut = createLogoutFunction(store, dispatch, history);\t\t\t\t\t\t\t\t\t// this component is rendered after login (so a user session will exist)\r\n\r\n\treturn {active, logOut, messages, children};\r\n}\r\n\r\nInactivityDetector = connect(mapStoreToProps, mapDispatchToProps, mergeProps)(InactivityDetector);\r\n\r\n\r\n// InactivityDetector (connected to router)\r\n// -----------------------------------------------------------------------\r\nInactivityDetector = withRouter(InactivityDetector)\r\nInactivityDetector = withMessages(InactivityDetector);\r\n\r\n\r\n// Export\r\n// -----------------------------------------------------------------------\r\nexport {InactivityDetector}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// utils\r\nimport {ENDPOINTS} from 'libs/api/constants'\r\nimport {api, REQUEST_STATES} from 'libs/api/api'\r\nimport {storedUsers} from 'libs/browser_storage/apis/users'\r\nimport {storedExams} from 'libs/browser_storage/apis/exams'\r\nimport {localStorageApi, KEYS} from 'libs/browser_storage/local-storage-api'\r\nimport {networkRequestLog} from 'libs/browser_storage/apis/network-request-log'\r\n\r\n// redux (selectors)\r\nimport {getUserSessionData} from 'redux/reducers/session/selectors'\r\nimport {getSessionData, getExamData} from 'redux/reducers/selectors'\r\nimport {getName as getExamName, getGuid as getExamGuid} from 'redux/reducers/exam/selectors'\r\nimport {getUserId, getUserGuid, getName as getUserName} from 'redux/reducers/session/user/selectors'\r\n\r\n\r\nconst IGNORED_ENDPOINTS = [\r\n\tENDPOINTS.PING,\r\n\tENDPOINTS.LOG_ERROR,\r\n\tENDPOINTS.BUG_REPORT,\r\n\tENDPOINTS.ASSESSMENT.SAVE_TIME\r\n];\r\n\r\n\r\n// ApiRequest = {\r\n// \tid: \"string\",\r\n// endpoint: \"string\",\r\n// payload: {} || null,\r\n// statusCode: Number || null,\r\n// type: \"POST|GET|DELETE|DOWNLOAD\"\r\n// }\r\n\r\n\r\n// RequestLog = {\r\n// \tid: \"string\",\r\n// \tendpoint: \"string\",\r\n// \tpayload: \"string?\",\r\n// \ttype: \"POST|GET|DELETE|DOWNLOAD\",\r\n// \ttimeStamp: Number (in ms),\r\n// \tuserGuid: \"string\",\r\n// \texamGuid: \"string?\",\r\n// status: \"string description or status code\",\r\n// \tresponse: \"\",\r\n// \tresponseHeaders: \"\"\r\n// }\r\n\r\n\r\nclass NetworkRequestLogger extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tconst shouldIgnoreRequest = (request) => {\r\n\t\t\treturn IGNORED_ENDPOINTS.some(endpoint => {\r\n\t\t\t\treturn request.endpoint === endpoint;\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tconst convertToRequestLog = (requestState, {statusCode, ...apiRequest}) => ({\r\n\t\t\t...apiRequest,\r\n\t\t\ttimeStamp: Date.now(),\r\n\t\t\tuserGuid: this.props.userGuid,\r\n\t\t\tstatus: statusCode || requestState,\r\n\t\t\texamGuid: this.props.examGuid || null\r\n\t\t});\r\n\r\n\t\tconst storeUserAndExam = () => {\r\n\t\t\tconst {userGuid, userName} = this.props;\r\n\t\t\tstoredUsers.set(userGuid, {name: userName});\r\n\r\n\t\t\tconst {examGuid, examName} = this.props;\r\n\t\t\tif (examGuid) {\r\n\t\t\t\tstoredExams.set(examGuid, {name: examName});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.logNetworkRequest = (requestState, apiRequest) => {\r\n\t\t\tif (shouldIgnoreRequest(apiRequest)) { return; }\r\n\t\t\tconst requestLog = convertToRequestLog(requestState, apiRequest);\r\n\t\t\tstoreUserAndExam();\r\n\t\t\tnetworkRequestLog.push(requestLog.id, requestLog);\r\n\t\t}\r\n\t}\r\n\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tthis.unsubscribeFromApi = api.subscribe(this.logNetworkRequest);\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tthis.unsubscribeFromApi();\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn this.props.children || null;\r\n\t}\r\n}\r\n\r\nNetworkRequestLogger.propTypes = {\r\n\tchildren: PropTypes.node,\r\n\tuserId: PropTypes.number.isRequired,\r\n\tuserGuid: PropTypes.string.isRequired,\r\n\tuserName: PropTypes.string.isRequired,\r\n\texamGuid: PropTypes.string,\r\n\texamName: PropTypes.string\r\n}\r\n\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\tconst sessionData = getSessionData(store);\r\n\tconst userSessionData = getUserSessionData(sessionData);\r\n\r\n\tconst examData = getExamData(store);\r\n\r\n\treturn {\r\n\t\tuserId: getUserId(userSessionData),\r\n\t\tuserGuid: getUserGuid(userSessionData),\r\n\t\tuserName: getUserName(userSessionData),\r\n\t\texamGuid: getExamGuid(examData),\r\n\t\texamName: getExamName(examData)\r\n\t}\r\n}\r\n\r\nNetworkRequestLogger = connect(mapStoreToProps)(NetworkRequestLogger);\r\n\r\n\r\nexport {NetworkRequestLogger}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// utils\r\nimport {scheduledBugReports} from 'libs/browser_storage/apis/scheduled-bug-reports'\r\n\r\n\r\nconst SEND_INTERVAL = 10000;\r\n\r\n\r\nclass ScheduledBugReportSender extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tscheduledBugReports.processQueue();\t\r\n\r\n\t\tthis.timeoutId = setInterval(() => {\r\n\t\t\tscheduledBugReports.processQueue();\r\n\t\t}, SEND_INTERVAL);\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn this.props.children;\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tclearInterval(this.timeoutId);\r\n\t}\r\n}\r\n\r\nScheduledBugReportSender.propTypes = {\r\n\tchildren: PropTypes.node.isRequired\r\n}\r\n\r\n\r\nexport {ScheduledBugReportSender}","\r\nimport PromiseQueue from 'promise-queue'\r\n\r\n\r\nconst MAX_CONCURRENT_ITEMS = 1;\r\nconst MAX_QUEUE_SIZE = Infinity;\r\n\r\n\r\nconst examApiQueue = new PromiseQueue(MAX_CONCURRENT_ITEMS, MAX_QUEUE_SIZE);\r\n\r\n\r\nexport {examApiQueue}","import { check } from \"@xams-utils/check-types\";\r\nimport { activityLogger } from \"libs/activity_logger/activity-logger\";\r\n\r\nconst removeActiviesFromPayload = (_payload) => {\r\n const payload = {};\r\n Object.keys(_payload).forEach(key => {\r\n if (key!=='activities') payload[key] = _payload[key];\r\n });\r\n return {...payload}; \r\n};\r\n\r\nconst addActiviesToPayLoad = (_payload) => {\r\n\r\n return new Promise((resolve) => {\r\n if (_payload.activities) resolve(_payload);\r\n const payload = { ..._payload };\r\n const {formRunGuid} = payload;\r\n activityLogger.get(formRunGuid).then((activities) => {\r\n payload.activities = activities;\r\n resolve(payload);\r\n });\r\n });\r\n};\r\n\r\nconst removeFormRunGuidFromActivityLog=(payload)=>{\r\n\r\n if (!payload) return Promise.resolve();\r\n\r\n const {formRunGuid} = payload;\r\n\r\n if (!formRunGuid) return Promise.resolve();\r\n \r\n return activityLogger.remove(formRunGuid);\r\n}\r\n\r\nconst _getErrorObject=(type, error)=>{\r\n const {responseText, status} = error;\r\n const obj = {_type: type, responseText, status};\r\n return obj; \r\n}\r\n\r\nconst getErrorObject=(errorObj)=>{\r\n var obj={};\r\n \r\n if (errorObj){\r\n for (let key in errorObj) {\r\n obj[key] = errorObj[key];\r\n }\r\n }\r\n\r\n return obj;\r\n}\r\n\r\nexport { addActiviesToPayLoad, removeActiviesFromPayload, removeFormRunGuidFromActivityLog, getErrorObject };\r\n","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { check } from \"@xams-utils/check-types\";\r\n\r\n// xams-utils\r\nimport { IdGenerator } from \"@xams-utils/id-generator\";\r\n\r\n// material-ui\r\nimport Button from \"@material-ui/core/Button\";\r\n\r\n// react\r\nimport { Popup } from \"components/layout/popup\";\r\nimport { ExamRequestSaverContext } from \"./context\";\r\n\r\n// redux (selectors)\r\nimport { getGuid as getExamGuid } from \"redux/reducers/exam/selectors\";\r\nimport {\r\n getAppData,\r\n getSessionData,\r\n getExamData,\r\n} from \"redux/reducers/selectors\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getUserGuid } from \"redux/reducers/session/user/selectors\";\r\nimport { isOfflineMode } from \"redux/reducers/app/selectors\";\r\n\r\n// redux (actions)\r\nimport { setOfflineMode } from \"./actions\";\r\n\r\n// utils\r\nimport { examApiQueue } from \"./exam-api-queue\";\r\nimport { generalApi } from \"libs/api/interface/api-general\";\r\nimport { assessmentApi } from \"libs/api/interface/api-assessment\";\r\nimport { localStorageApi, KEYS } from \"libs/browser_storage/local-storage-api\";\r\nimport { networkRequestLog } from \"libs/browser_storage/apis/network-request-log\";\r\nimport {\r\n addActiviesToPayLoad,\r\n removeActiviesFromPayload,\r\n getErrorObject,\r\n} from \"./provider-activity-logger\";\r\n\r\n// utils (bodge)\r\nimport { api } from \"libs/api/api\";\r\nimport { ENDPOINTS, GENERIC_ERRORS } from \"libs/api/constants\";\r\nimport { ApiRequest } from \"libs/api/api-request\";\r\nimport {\r\n activityLogger,\r\n ACTIVITIES,\r\n} from \"libs/activity_logger/activity-logger\";\r\n\r\n//import { uploadOfflineFiles } from \"libs/offline_uploader/offline_uploader\";\r\n\r\nconst TYPES = {\r\n SAVE_ANSWER: 1,\r\n SAVE_TIME: 2,\r\n COMPLETE_EXAM: 3,\r\n SAVE_ACTIVITY_LOG: 4,\r\n};\r\n\r\nconst APIS = {\r\n [TYPES.SAVE_ANSWER]: assessmentApi,\r\n [TYPES.SAVE_TIME]: assessmentApi,\r\n [TYPES.COMPLETE_EXAM]: assessmentApi,\r\n [TYPES.SAVE_ACTIVITY_LOG]: assessmentApi,\r\n};\r\n\r\nconst FUNCTIONS = {\r\n [TYPES.SAVE_ANSWER]: \"saveAnswer\",\r\n [TYPES.SAVE_TIME]: \"saveExamTime\",\r\n [TYPES.COMPLETE_EXAM]: \"finishExam\",\r\n [TYPES.SAVE_ACTIVITY_LOG]: \"saveActivityLog\",\r\n};\r\n\r\n// Not connected to store\r\n// --------------------------------------------------------------------------\r\n\r\nclass ExamRequestSaverProvider extends React.Component {\r\n constructor(props) {\r\n super(props);\r\n this.state = { showPopup: false };\r\n\r\n this.initializeBoundMethods();\r\n\r\n this.providerValue = {\r\n saveAnswer: this.saveAnswer,\r\n saveExamTime: this.saveExamTime,\r\n completeExam: this.completeExam,\r\n saveActivityLog: this.saveActivityLog,\r\n };\r\n\r\n this.callbacks = {}; // {id: {onSuccess, onFail}}\r\n }\r\n\r\n initializeBoundMethods() {\r\n // TEMP\r\n // ------------------------------------------------------\r\n const getEndpoint = (type, payload) => {\r\n switch (type) {\r\n case TYPES.SAVE_ANSWER:\r\n return ENDPOINTS.ASSESSMENT.SAVE_ANSWER;\r\n case TYPES.SAVE_TIME:\r\n return ENDPOINTS.ASSESSMENT.SAVE_TIME;\r\n case TYPES.COMPLETE_EXAM:\r\n return ENDPOINTS.ASSESSMENT.COMPLETE;\r\n case TYPES.SAVE_ACTIVITY_LOG: {\r\n const { formRunGuid } = payload;\r\n return `${ENDPOINTS.ASSESSMENT.SAVE_ACTIVITY_LOG}/${formRunGuid}`;\r\n }\r\n default:\r\n break;\r\n }\r\n\r\n // return type === TYPES.SAVE_ANSWER\r\n // ? ENDPOINTS.ASSESSMENT.SAVE_ANSWER\r\n // : type === TYPES.SAVE_TIME\r\n // ? ENDPOINTS.ASSESSMENT.SAVE_TIME\r\n // : ENDPOINTS.ASSESSMENT.COMPLETE;\r\n };\r\n\r\n const logRequest = (type, payload, id, state) => {\r\n const endpoint = getEndpoint(type, payload);\r\n const apiRequest = new ApiRequest(\"POST\", endpoint, 5, id);\r\n const _payload =\r\n type === TYPES.SAVE_ACTIVITY_LOG\r\n ? removeActiviesFromPayload({ ...payload })\r\n : payload;\r\n apiRequest.setPayload(_payload);\r\n api.emitEvent(apiRequest, state);\r\n };\r\n\r\n const getLocalExamQueue = (localExamQueues) => {\r\n const { userGuid, examGuid } = this.props;\r\n\r\n if (!localExamQueues[userGuid]) {\r\n localExamQueues[userGuid] = {};\r\n }\r\n\r\n const usersQueues = localExamQueues[userGuid];\r\n if (!usersQueues[examGuid]) {\r\n usersQueues[examGuid] = [];\r\n }\r\n\r\n return usersQueues[examGuid];\r\n };\r\n\r\n const saveToLocalStorage = (key, data) => {\r\n while (true) {\r\n try {\r\n localStorageApi.saveDataTo(key, data);\r\n break;\r\n } catch (e) {\r\n networkRequestLog.clearSpace();\r\n }\r\n }\r\n };\r\n\r\n const storeRequestLocally = (type, _payload, id) => {\r\n const localExamQueues = localStorageApi.retrieveDataFrom(\r\n KEYS.EXAM_QUEUES\r\n );\r\n const examQueue = getLocalExamQueue(localExamQueues);\r\n const payload =\r\n type === TYPES.SAVE_ACTIVITY_LOG\r\n ? removeActiviesFromPayload({ ..._payload })\r\n : _payload;\r\n examQueue.push({ type, payload, id });\r\n\r\n saveToLocalStorage(KEYS.EXAM_QUEUES, localExamQueues);\r\n //localStorageApi.saveDataTo(KEYS.EXAM_QUEUES, localExamQueues);\r\n };\r\n\r\n const removeRequestLocally = (id) => {\r\n const localExamQueues = localStorageApi.retrieveDataFrom(\r\n KEYS.EXAM_QUEUES\r\n );\r\n const examQueue = getLocalExamQueue(localExamQueues);\r\n const requestIndex = examQueue.findIndex((item) => item.id === id);\r\n examQueue.splice(requestIndex, 1);\r\n\r\n if (examQueue.length === 0) {\r\n const userQueues = localExamQueues[this.props.userGuid];\r\n delete userQueues[this.props.examGuid];\r\n if (Object.keys(userQueues).length === 0) {\r\n delete localExamQueues[this.props.userGuid];\r\n }\r\n }\r\n saveToLocalStorage(KEYS.EXAM_QUEUES, localExamQueues);\r\n //localStorageApi.saveDataTo(KEYS.EXAM_QUEUES, localExamQueues);\r\n };\r\n\r\n const storeFailedRequestLocally = (\r\n type,\r\n _payload,\r\n id,\r\n fromTrySendRequest = false\r\n ) => {\r\n const payload =\r\n type === TYPES.SAVE_ACTIVITY_LOG\r\n ? removeActiviesFromPayload({ ..._payload })\r\n : _payload;\r\n logRequest(type, payload, id, \"SAVED LOCALLY\");\r\n\r\n const { queue = [] } = localStorageApi.retrieveDataFrom(\r\n KEYS.FAILED_EXAM_REQUESTS\r\n );\r\n queue.push({ type, payload, id });\r\n saveToLocalStorage(KEYS.FAILED_EXAM_REQUESTS, { queue });\r\n\r\n if (type === TYPES.SAVE_ACTIVITY_LOG && fromTrySendRequest) {\r\n const { onFail } = this.callbacks[id] || {};\r\n if (onFail) {\r\n onFail({});\r\n }\r\n }\r\n //localStorageApi.saveDataTo(KEYS.FAILED_EXAM_REQUESTS, {queue});\r\n };\r\n\r\n const moveStoredRequestsToFailed = () => {\r\n const localExamQueues = localStorageApi.retrieveDataFrom(\r\n KEYS.EXAM_QUEUES\r\n );\r\n const examQueue = getLocalExamQueue(localExamQueues);\r\n examQueue.forEach(({ type, payload, id }) => {\r\n storeFailedRequestLocally(type, payload, id);\r\n });\r\n examQueue.splice(0, examQueue.length);\r\n\r\n saveToLocalStorage(KEYS.EXAM_QUEUES, localExamQueues);\r\n //localStorageApi.saveDataTo(KEYS.EXAM_QUEUES, localExamQueues);\r\n };\r\n // ------------------------------------------------------\r\n\r\n const sendRequest = (type, payload, id) => {\r\n logRequest(type, payload, id, \"QUEUED\"); // TEMP BODGE\r\n const api = APIS[type];\r\n const func = FUNCTIONS[type];\r\n\r\n storeRequestLocally(type, payload, id);\r\n examApiQueue.add(() => {\r\n if (this.props.offline || this.offline) {\r\n return Promise.resolve();\r\n }\r\n\r\n const onSuccess = (response) => {\r\n removeRequestLocally(id);\r\n const { onSuccess } = this.callbacks[id] || {};\r\n if (onSuccess) {\r\n onSuccess(response);\r\n }\r\n };\r\n\r\n const onAcivityLogSuccess = (response) => {\r\n const { formRunGuid } = payload;\r\n\r\n activityLogger.sent(formRunGuid).then(() => {\r\n onSuccess(response);\r\n });\r\n //onSuccess(response);\r\n // removeFormRunGuidFromActivityLog(payload).then(() => {\r\n // onSuccess(response);\r\n // });\r\n };\r\n\r\n const onFail = (error) => {\r\n if (error === GENERIC_ERRORS.DOUBLE_LOGIN_THROW) {\r\n removeRequestLocally(id);\r\n } else {\r\n this.offline = true;\r\n moveStoredRequestsToFailed();\r\n\r\n const errorObj = getErrorObject(error);\r\n\r\n const offlineReason = {\r\n offline: true,\r\n error: errorObj,\r\n };\r\n\r\n if (type !== TYPES.SAVE_ACTIVITY_LOG) {\r\n offlineReason.func = func;\r\n offlineReason.payload = payload;\r\n offlineReason.id = id;\r\n }\r\n\r\n this.props.setOfflineMode(offlineReason);\r\n\r\n activityLogger.log(ACTIVITIES.OFFLINE, {\r\n error: errorObj,\r\n _type: type,\r\n func,\r\n payload,\r\n });\r\n }\r\n\r\n const { onFail } = this.callbacks[id] || {};\r\n if (onFail) {\r\n onFail(error);\r\n }\r\n };\r\n\r\n if (type === TYPES.SAVE_ACTIVITY_LOG) {\r\n addActiviesToPayLoad(payload).then((payload) => {\r\n api[func](payload, id).then(\r\n onAcivityLogSuccess,\r\n onFail\r\n );\r\n });\r\n } else {\r\n return api[func](payload, id).then(onSuccess, onFail);\r\n }\r\n });\r\n };\r\n\r\n this.trySendRequest = (type, payload, id) => {\r\n if (this.props.offline) {\r\n storeFailedRequestLocally(type, payload, id, true);\r\n } else {\r\n sendRequest(type, payload, id);\r\n // if (type === TYPES.COMPLETE_EXAM) {\r\n // const { formRunGuid } = payload;\r\n\r\n // uploadOfflineFiles(formRunGuid)\r\n // .then(() => {\r\n // sendRequest(type, payload, id);\r\n // })\r\n // .catch((e) => {\r\n // debugger;\r\n // sendRequest(type, payload, id);\r\n // });\r\n // } else {\r\n // sendRequest(type, payload, id);\r\n // }\r\n }\r\n };\r\n\r\n this.moveAllExamQueusToFailedRequests = () => {\r\n const examQueues = localStorageApi.retrieveDataFrom(\r\n KEYS.EXAM_QUEUES\r\n );\r\n const { queue = [] } = localStorageApi.retrieveDataFrom(\r\n KEYS.FAILED_EXAM_REQUESTS\r\n );\r\n const oldQueueLength = queue.length;\r\n\r\n Object.keys(examQueues).forEach((userGuid) => {\r\n Object.keys(examQueues[userGuid]).forEach((examGuid) => {\r\n examQueues[userGuid][examGuid].forEach(\r\n ({ type, payload }) => {\r\n queue.push({\r\n type,\r\n payload,\r\n id: IdGenerator.uuid(),\r\n });\r\n }\r\n );\r\n });\r\n });\r\n\r\n if (oldQueueLength !== queue.length) {\r\n saveToLocalStorage(KEYS.FAILED_EXAM_REQUESTS, { queue });\r\n //localStorageApi.saveDataTo(KEYS.FAILED_EXAM_REQUESTS, {queue});\r\n localStorageApi.resetDataFor(KEYS.EXAM_QUEUES);\r\n }\r\n };\r\n\r\n this.flushFailedRequests = (createNewIds = false) => {\r\n const { queue } = localStorageApi.retrieveDataFrom(\r\n KEYS.FAILED_EXAM_REQUESTS\r\n );\r\n\r\n if (queue) {\r\n queue.forEach(({ type, payload, id }) => {\r\n if (createNewIds) {\r\n id = IdGenerator.uuid();\r\n }\r\n\r\n payload.IsFromLocalStorage = true;\r\n\r\n this.trySendRequest(type, payload, id);\r\n });\r\n }\r\n\r\n localStorageApi.resetDataFor(KEYS.FAILED_EXAM_REQUESTS);\r\n };\r\n\r\n const generateCallbacksId = (callbacks = {}) => {\r\n const id = IdGenerator.uuid();\r\n this.callbacks[id] = callbacks;\r\n return id;\r\n };\r\n\r\n this.saveAnswer = (payload, callbacks) => {\r\n const id = generateCallbacksId(callbacks);\r\n this.trySendRequest(TYPES.SAVE_ANSWER, payload, id);\r\n };\r\n\r\n this.saveExamTime = (payload, callbacks) => {\r\n const id = generateCallbacksId(callbacks);\r\n this.trySendRequest(TYPES.SAVE_TIME, payload, id);\r\n };\r\n\r\n this.completeExam = (payload, callbacks) => {\r\n const id = generateCallbacksId(callbacks);\r\n\r\n this.trySendRequest(TYPES.COMPLETE_EXAM, payload, id);\r\n };\r\n\r\n this.saveActivityLog = (payload, callbacks) => {\r\n const id = generateCallbacksId(callbacks);\r\n this.trySendRequest(TYPES.SAVE_ACTIVITY_LOG, payload, id);\r\n };\r\n\r\n this.startPinging = () => {\r\n this.intervalId = setInterval(() => {\r\n generalApi.ping().then(() => {\r\n this.props.setOfflineMode(false);\r\n activityLogger.log(ACTIVITIES.ONLINE, {});\r\n clearInterval(this.intervalId);\r\n });\r\n }, this.props.pingInterval);\r\n };\r\n }\r\n\r\n componentDidMount() {\r\n activityLogger.initialise().then(() => {\r\n this.moveAllExamQueusToFailedRequests();\r\n const { queue } = localStorageApi.retrieveDataFrom(\r\n KEYS.FAILED_EXAM_REQUESTS\r\n );\r\n\r\n if (queue) {\r\n this.flushFailedRequests(true);\r\n this.setState({ showPopup: true });\r\n }\r\n });\r\n }\r\n\r\n componentDidUpdate(previousProps) {\r\n if (previousProps.offline !== this.props.offline) {\r\n if (this.props.offline) {\r\n this.startPinging();\r\n } else {\r\n this.offline = false;\r\n this.flushFailedRequests();\r\n }\r\n }\r\n }\r\n\r\n render() {\r\n return (\r\n \r\n \r\n {this.props.children}\r\n \r\n {this.Popup}\r\n \r\n );\r\n }\r\n\r\n get Popup() {\r\n if (this.state.showPopup) {\r\n const title = \"Unsaved answers have been synced!\";\r\n const text =\r\n \"We noticed that a connection was lost while a previous \" +\r\n \"exam was running. Any answers made during that time have \" +\r\n \"now been synced to XAMS.\";\r\n\r\n const onClick = () => this.setState({ showPopup: false });\r\n const okButton = (\r\n \r\n {\"OK\"}\r\n \r\n );\r\n\r\n return (\r\n \r\n );\r\n }\r\n\r\n return null;\r\n }\r\n}\r\n\r\nExamRequestSaverProvider.defaultProps = {\r\n pingInterval: 30000,\r\n};\r\n\r\nExamRequestSaverProvider.propTypes = {\r\n offline: PropTypes.bool.isRequired,\r\n pingInterval: PropTypes.number.isRequired,\r\n setOfflineMode: PropTypes.func.isRequired,\r\n userGuid: PropTypes.string.isRequired,\r\n // examGuid: PropTypes.string.isRequired,\r\n};\r\n\r\n// Connected to store\r\n// --------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n offline: !!isOfflineMode(getAppData(store)),\r\n userGuid: getUserGuid(getUserSessionData(getSessionData(store))),\r\n examGuid: getExamGuid(getExamData(store)),\r\n});\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n setOfflineMode: (value) => dispatch(setOfflineMode(value)),\r\n});\r\n\r\nExamRequestSaverProvider = connect(\r\n mapStoreToProps,\r\n mapDispatchToProps\r\n)(ExamRequestSaverProvider);\r\n\r\n// Export\r\n// --------------------------------------------------------------------------\r\nexport { ExamRequestSaverProvider };\r\n","\r\nimport {SET_OFFLINE_MODE} from 'redux/reducers/app/action-types'\r\n\r\n\r\nconst setOfflineMode = (value) => ({\r\n\ttype: SET_OFFLINE_MODE,\r\n\tvalue\r\n});\r\n\r\n\r\nexport {setOfflineMode}","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { PROCTORING_TYPES } from \"constants/proctoring\";\r\nimport { getUserSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getUserId } from \"redux/reducers/session/user/selectors\";\r\nimport { getSessionData, getProctorioData } from \"redux/reducers/selectors\";\r\nimport { getDebugMode } from \"redux/reducers/proctorio/selectors\";\r\n\r\nimport { assessmentApi } from \"libs/api/interface/api-assessment\";\r\n\r\nclass RedirectToProctorio extends Component {\r\n state = {};\r\n\r\n componentDidMount() {\r\n const { debugMode, examGuid, proctorProvider } = this.props;\r\n\r\n if (proctorProvider === PROCTORING_TYPES.PROCTORIO) {\r\n if (debugMode) {\r\n const userName = \"\";\r\n const url = `http://localhost:3000/proctorio/sfjuk/benanderson/Debu2476/${examGuid}`;\r\n console.log(url);\r\n\r\n window.location.replace(url);\r\n } else {\r\n const { userID, proctoringSessionID, onError } = this.props;\r\n\r\n let options = \"\";\r\n options += \"recordvideo\";\r\n options += \",recordaudio\";\r\n options += \",recordscreen\";\r\n options += \",recordwebtraffic\";\r\n options += \",recordroomstart\";\r\n options += \",verifyvideo\";\r\n options += \",verifyaudio\";\r\n options += \",verifydesktop\";\r\n options += \",verifyidauto\";\r\n options += \",verifysignature\";\r\n options += \",fullscreenmoderate\";\r\n options += \",clipboard\";\r\n options += \",notabs\";\r\n options += \",closetabs\";\r\n options += \",onescreen\";\r\n options += \",print\";\r\n options += \",downloads\";\r\n //options += \",cache\";\r\n options += \",rightclick\";\r\n options += \",noreentry\";\r\n\r\n const host = window.location.host;\r\n\r\n assessmentApi\r\n .getProctorioLaunchUrl(userID, proctoringSessionID, options, host)\r\n .then((url) => {\r\n //console.log(\"Proctorio URL\");\r\n //console.log(url);\r\n //debugger;\r\n //logout();\r\n window.location.replace(url);\r\n }, onError)\r\n .catch(() => {\r\n onError();\r\n });\r\n }\r\n }\r\n }\r\n\r\n render() {\r\n return null;\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const userSessionData = getUserSessionData(getSessionData(store));\r\n const proctorioData = getProctorioData(store);\r\n\r\n return {\r\n userID: getUserId(userSessionData),\r\n debugMode: getDebugMode(proctorioData),\r\n };\r\n};\r\n\r\nRedirectToProctorio = connect(mapStoreToProps)(RedirectToProctorio);\r\n\r\nexport { RedirectToProctorio };\r\n","// npm\r\nimport React from \"react\";\r\n\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getClientSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getDemoMode } from \"redux/reducers/settings/client/selectors\";\r\n\r\n// react\r\nimport { Popup } from \"components/layout/popup\";\r\nimport { CloseErrorButton } from \"components/pages/exam/initialization/close-error-button\";\r\n\r\n// messages\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nlet CantRedirectPopup = ({ messages, demoMode }) => {\r\n const props = {\r\n title: 'Error',\r\n content: { text: \"Unable to Redirect to Proctorio\" },\r\n //buttons: [ ],\r\n canClickBackdrop: false,\r\n };\r\n\r\n if (!demoMode) {\r\n props.buttons = [ ];\r\n }\r\n\r\n return ;\r\n};\r\n\r\nCantRedirectPopup = withMessages(CantRedirectPopup);\r\n\r\nconst mapStoreToProps = (store) => {\r\n const settingsData = getSettingsData(store);\r\n const clientSettingsData = getClientSettingsData(settingsData);\r\n\r\n return {\r\n demoMode: getDemoMode(clientSettingsData),\r\n };\r\n};\r\n\r\nCantRedirectPopup = connect(mapStoreToProps)(CantRedirectPopup);\r\n\r\nexport { CantRedirectPopup };\r\n","\r\nimport {Machine} from 'xstate'\r\nimport {XStateConfig} from './xstate-config'\r\n\r\n\r\n// #########################################\r\n// STATE NAMES\r\n// #########################################\r\n\r\nconst STATES = {\r\n\tCHECKING_SCHEDULE_COMPLETION : 'checking_schedule_completion',\r\n\tSCHEDULE_ALREADY_COMPLETED: 'schedule_aready_completed',\r\n\tCHECKING_SCHEDULE_CAN_START: 'checking_schedule_can_start',\r\n\tSCHEDULE_CANT_START: 'schedule_cant_start',\r\n\tREDIRECT_TO_PROCTORIO: 'redirect_to_proctorio',\r\n\tCANT_REDIRECT: 'cant_redirect'\r\n}\r\n\r\n\r\n// #########################################\r\n// EVENT NAMES\r\n// #########################################\r\n\r\nconst EVENTS = {\r\n\tSCHEDULE_INCOMPLETE: 'schedule_incomplete',\r\n\tSCHEDULE_COMPLETE: 'schedule_complete',\r\n\tSCHEDULE_CANT_START: 'schedule_cant_start',\r\n\tSCHEDULE_CAN_START: 'schedule_can_start',\r\n\tERROR: 'error',\r\n}\r\n\r\n\r\n// #########################################\r\n// STATES\r\n// #########################################\r\n\r\nconst checkingScheduleCompletion = new XStateConfig();\r\ncheckingScheduleCompletion.addTransition(EVENTS.SCHEDULE_COMPLETE, STATES.SCHEDULE_ALREADY_COMPLETED);\r\ncheckingScheduleCompletion.addTransition(EVENTS.SCHEDULE_INCOMPLETE, STATES.CHECKING_SCHEDULE_CAN_START);\r\n\r\nconst scheduleAlreadyComplete = new XStateConfig();\r\n\r\nconst checkingScheduleCanStart = new XStateConfig();\r\ncheckingScheduleCanStart.addTransition(EVENTS.SCHEDULE_CANT_START, STATES.SCHEDULE_CANT_START);\r\ncheckingScheduleCanStart.addTransition(EVENTS.SCHEDULE_CAN_START, STATES.REDIRECT_TO_PROCTORIO);\r\n\r\nconst scheduleCantStart = new XStateConfig();\r\n\r\nconst redirectToProctori = new XStateConfig();\r\nredirectToProctori.addTransition(EVENTS.ERROR, STATES.CANT_REDIRECT);\r\n\r\nconst cantRedirect = new XStateConfig();\r\n\r\n\r\n// #########################################\r\n// MACHINE\r\n// #########################################\r\n\r\nconst _proctorioRedirect = new XStateConfig();\r\n_proctorioRedirect.initialState = STATES.CHECKING_SCHEDULE_COMPLETION;\r\n_proctorioRedirect.addState(STATES.CHECKING_SCHEDULE_COMPLETION, checkingScheduleCompletion);\r\n_proctorioRedirect.addState(STATES.SCHEDULE_ALREADY_COMPLETED, scheduleAlreadyComplete);\r\n_proctorioRedirect.addState(STATES.CHECKING_SCHEDULE_CAN_START, checkingScheduleCanStart);\r\n_proctorioRedirect.addState(STATES.SCHEDULE_CANT_START, scheduleCantStart);\r\n_proctorioRedirect.addState(STATES.REDIRECT_TO_PROCTORIO, redirectToProctori);\r\n_proctorioRedirect.addState(STATES.CANT_REDIRECT, cantRedirect);\r\n\r\nconst machine = Machine(_proctorioRedirect.toObject());\r\nmachine.id = \"Redirect To Proctorio Machine\";\r\n\r\n\r\n// #########################################\r\n// EXPORT\r\n// #########################################\r\n\r\nconst proctorioRedirect = {\r\n\tmachine,\r\n\tEVENTS: {...EVENTS},\r\n\tSTATES: {...STATES}\r\n}\r\n\r\nexport {proctorioRedirect}","// npm\r\nimport React from \"react\";\r\n\r\n// xams-components\r\nimport {\r\n StateMachine,\r\n StateView,\r\n StateControl,\r\n} from \"temp/xams.UI.Components/machine\";\r\n\r\n// react\r\nimport { CanStartChecker } from \"components/pages/_exam/initializing/validating_schedule/can-start-checker\";\r\nimport { ScheduleCompletedPopup } from \"components/pages/_exam/initializing/validating_schedule/completed-popup\";\r\nimport { ScheduleCantStartPopup } from \"components/pages/_exam/initializing/validating_schedule/cant-start-popup\";\r\nimport { ScheduleCompletionChecker } from \"components/pages/_exam/initializing/validating_schedule/completion-checker\";\r\nimport { RedirectToProctorio } from \"./redirect-to-proctorio\";\r\nimport { CantRedirectPopup } from \"./cant-redirect-popup\";\r\n\r\n// machines\r\nimport { proctorioRedirect } from \"machines/proctorio-redirect\";\r\n\r\nconst { EVENTS, STATES } = proctorioRedirect;\r\n\r\nconst RedirectProctorioMachine = ({\r\n examGuid,\r\n proctorProvider,\r\n proctoringSessionID,\r\n}) => {\r\n return (\r\n \r\n \r\n {{\r\n [STATES.CHECKING_SCHEDULE_COMPLETION]: () => (\r\n \r\n {(props) => (\r\n \r\n )}\r\n \r\n ),\r\n [STATES.SCHEDULE_ALREADY_COMPLETED]: () => (\r\n \r\n ),\r\n [STATES.CHECKING_SCHEDULE_CAN_START]: () => (\r\n \r\n {(props) => (\r\n \r\n )}\r\n \r\n ),\r\n [STATES.SCHEDULE_CANT_START]: () => (\r\n \r\n ),\r\n [STATES.REDIRECT_TO_PROCTORIO]: () => (\r\n \r\n {(props) => (\r\n \r\n )}\r\n \r\n ),\r\n [STATES.CANT_REDIRECT]: () => ,\r\n }}\r\n \r\n \r\n );\r\n};\r\n\r\nexport { RedirectProctorioMachine };\r\n\r\n// http://localhost:3000/proctorio/sfjuk/benanderson/5v!b&pm+/2141e118-6d3a-430f-8079-3e766b650f16\r\n","import React, { Component } from \"react\";\r\n\r\nimport { AppPageModifier } from \"components/pages/app-page-modifier\";\r\n\r\nimport { RedirectProctorioMachine } from \"./redirect-machine\";\r\n\r\nclass RedirectProctorioMachinePage extends Component {\r\n state = {};\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n const {\r\n match: { params },\r\n } = props;\r\n\r\n this.proctorProvider = params.proctorProvider;\r\n this.proctoringSessionID = params.proctoringSessionID;\r\n this.examGuid = params.examGuid;\r\n }\r\n\r\n render() {\r\n const appBarProps = {\r\n title: \"Proctorio Initialisation\",\r\n loadingTitle: true,\r\n logo: true,\r\n logout: false,\r\n };\r\n\r\n const machineProps = {\r\n proctorProvider: this.proctorProvider,\r\n proctoringSessionID: this.proctoringSessionID,\r\n examGuid: this.examGuid,\r\n };\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n );\r\n }\r\n}\r\n\r\nexport { RedirectProctorioMachinePage };\r\n","import React, { Component } from \"react\";\r\nimport { withRouter } from \"react-router-dom\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getProctorioData } from \"redux/reducers/selectors\";\r\nimport { getParent, getDebugMode, getClientName, getUserInfo1, getUserInfo2 } from \"redux/reducers/proctorio/selectors\";\r\nimport {encodeString} from \"../helper\"\r\n\r\n\r\nclass ProctorioEndPage extends Component {\r\n\r\n componentDidMount() {\r\n const {clientName, userInfo1, userInfo2, history} = this.props;\r\n const url = `/proctorio-return/${clientName}/${encodeString(userInfo1)}/${encodeString(userInfo2)}`;\r\n\r\n history.push(url);\r\n }\r\n\r\n\r\n render() {\r\n return null;\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const proctorioData = getProctorioData(store);\r\n\r\n return {proctorioActive: getParent(proctorioData),\r\n clientName: getClientName(proctorioData),\r\n userInfo1: getUserInfo1(proctorioData),\r\n userInfo2: getUserInfo2(proctorioData),\r\n proctorioDebug: getDebugMode(proctorioData)\r\n };\r\n};\r\n\r\nProctorioEndPage = connect(mapStoreToProps)(ProctorioEndPage);\r\n\r\nProctorioEndPage = withRouter(ProctorioEndPage);\r\n\r\nexport { ProctorioEndPage };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {Switch, Route, withRouter} from 'react-router-dom'\r\n\r\n// react\r\nimport {ExamPage} from './_exam/page'\r\nimport {CoursePage} from './e_learning/course/page'\r\nimport {AdminPage} from './admin/admin-page'\r\nimport {AdminRedirectIframe} from './login/admin_redirect/iframe'\r\nimport {RubricPage} from './rubric/page'\r\nimport {SchedulesPage} from './schedules/page'\r\nimport {ActivityLogsPage} from './activity_logs/activity-logs'\r\nimport {ActivityLogPage} from './activity_logs/activity-log'\r\nimport {InactivityDetector} from './inactivity-detector'\r\nimport {NetworkRequestLogger} from './network-request-logger'\r\nimport {ClientSettingsFetcher} from './client-settings-fetcher'\r\nimport {ScheduledBugReportSender} from './scheduled-bug-report-sender'\r\nimport {ExamRequestSaverProvider} from 'components/hocs/exam_request_saver/provider'\r\nimport {RedirectProctorioMachinePage} from 'components/pages/proctorio/redirect/redirect-machine-page'\r\nimport {ProctorioEndPage} from 'components/pages/proctorio/end/end-page'\r\n\r\n// utils\r\nimport {routingBlocker, BLOCKING_TYPES} from 'utils/routing-blocker'\r\n\r\n// messages\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nclass SessionPageRouter extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {settingsFetched: false};\r\n\r\n\t\tthis.unsubscribe = routingBlocker.subscribe((location, action) => {\r\n\t\t\tif (action !== \"POP\") { return; }\r\n\r\n\t\t\tconst currentPath = this.props.location.pathname;\r\n\t\t\tconst nextPath = location.pathname;\r\n\r\n\t\t\tif (!currentPath.includes('exam') && nextPath.includes('exam')) {\r\n\t\t\t\tconst {messages} = this.props;\r\n\r\n\t\t\t\treturn {\r\n\t\t\t\t\ttype: BLOCKING_TYPES.BLOCK,\r\n\t\t\t\t\tmessage: messages[MESSAGE_IDS.EXAM.CANT_NAVIGATE]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tif (!this.state.settingsFetched) {\r\n\t\t\treturn this.setState({settingsFetched: true})}/>;\r\n\t\t}\r\n\t\t\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t \r\n\t\t\t\t\t\t \r\n\t\t\t\t\t \r\n\t\t\t\t \r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tthis.unsubscribe();\r\n\t}\r\n}\r\n\r\nSessionPageRouter.propTypes = {\r\n\thistory: PropTypes.object.isRequired,\r\n\tlocation: PropTypes.object.isRequired\r\n}\r\n\r\nSessionPageRouter = withRouter(SessionPageRouter);\r\nSessionPageRouter = withMessages(SessionPageRouter);\r\n\r\n\r\nexport {SessionPageRouter}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {DoubleLogin} from './double-login'\r\nimport {TokenRefresher} from './token-refresher'\r\nimport {SessionPageRouter} from './session-page-router'\r\n\r\n\r\nconst SessionPageWrapper = () => (\r\n\t\r\n\t\t\r\n\t\t\t \r\n\t\t\t \r\n\t\t \r\n\t \r\n)\r\n\r\n\r\nexport {SessionPageWrapper}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// material-ui\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {Align} from 'components/layout/align'\r\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n\r\n// NoClientPage (not connected)\r\n// -------------------------------------------------------------------------------\r\n\r\nlet NoClientPage = ({classes}) => (\r\n\t\r\n\t\t \r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{\"Please use your client's login address\"}\r\n\t\t\t \r\n\t\t \r\n\t \r\n)\r\n\r\n\r\n// NoClientPage (connected to styles)\r\n// -------------------------------------------------------------------------------\r\n\r\nconst styles = ({palette}) => ({\r\n\ttext: {\r\n\t\tcolor: palette.background.contrastText\r\n\t}\r\n})\r\n\r\nNoClientPage = withStyles(styles)(NoClientPage);\r\n\r\n\r\n\r\nexport {NoClientPage}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// react\r\nimport {MultiImagePreloader} from 'components/functional/image_preloading/multi-image-preloader'\r\n\r\n// redux (selectors)\r\nimport {getSettingsData} from 'redux/reducers/selectors'\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getLogoUrl, getAppBarLogoUrl} from 'redux/reducers/settings/client/selectors'\r\n\r\n\r\n// LogoPreloader (not connected to store)\r\n// ----------------------------------------------\r\n\r\nlet LogoPreloader = (props) =>\r\n{\r\n\tconst {onSuccess, onFail} = props;\r\n\tconst urls = [props.logoUrl, props.appBarLogoUrl];\r\n\r\n\treturn ;\r\n}\r\n\r\nLogoPreloader.propTypes = {\r\n\tonSuccess: PropTypes.func.isRequired,\r\n\tonFail: PropTypes.func.isRequired,\r\n\tlogoUrl: PropTypes.string.isRequired,\r\n\tappBarLogoUrl: PropTypes.string.isRequired\r\n}\r\n\r\n\r\n// LogoPreloader (connected to store)\r\n// ----------------------------------------------\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\tconst clientSettingsData = getClientSettingsData(getSettingsData(store));\r\n\r\n\treturn {\r\n\t\tappBarLogoUrl: getAppBarLogoUrl(clientSettingsData),\r\n\t\tlogoUrl: getLogoUrl(clientSettingsData)\r\n\t}\r\n}\r\n\r\nLogoPreloader = connect(mapStoreToProps)(LogoPreloader);\r\n\r\n\r\n// Export\r\n// ----------------------------------------------\r\nexport {LogoPreloader}","\r\n// utils\r\nimport {api} from '../api'\r\nimport { activityLogger} from \"libs/activity_logger/activity-logger\";\r\n\r\n\r\nconst setActivityLogger=(value)=>{\r\n\tconst numValue = parseInt(value);\r\n\tif (!isNaN(numValue) && numValue>0 && numValue<3){\r\n\t\tactivityLogger.setMode(numValue);\r\n\t}\r\n}\r\n\r\nconst getConfig = () =>\r\n{\r\n\treturn api.get(\"/cosmog.json\").then((response) => {\r\n const parsedResponse = JSON.parse(response);\r\n\r\n\t\tapi.EndpointUrl = parsedResponse.apiEndpointUrl;\r\n\t\tapi.HubpointUrl = parsedResponse.hubEndpointUrl;\r\n api.AuthenticationUrl = parsedResponse.authenticationUrl;\r\n\r\n if (parsedResponse.activityLogger){\r\n setActivityLogger(parsedResponse.activityLogger);\r\n\t\t}\r\n\t\t\r\n return parsedResponse;\r\n });\r\n}\r\n\r\n\r\nconst configApi = {\r\n\tgetConfig\r\n}\r\n\r\nexport {configApi};","// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// libs\r\nimport {configApi} from 'libs/api/interface/api-config'\r\n\r\n// redux (actions)\r\nimport {saveSettings} from './actions'\r\n\r\n\r\n// SettingsInitializer (not connected to store)\r\n// --------------------------------------------\r\n\r\nclass SettingsInitializer extends React.Component\r\n{\r\n componentDidMount()\r\n {\r\n configApi.getConfig().then(this.props.saveSettings, this.props.onFail);\r\n }\r\n\r\n render()\r\n {\r\n return null;\r\n }\r\n}\r\n\r\nSettingsInitializer.propTypes = {\r\n saveSettings: PropTypes.func.isRequired,\r\n onFail: PropTypes.func.isRequired\r\n};\r\n\r\n\r\n// SettingsInitializer (connected to store)\r\n// --------------------------------------------\r\n\r\nconst mapDispatchToProps = (dispatch, {onSuccess}) => ({\r\n onSuccess: undefined,\r\n saveSettings: parsedResponse => {\r\n dispatch(saveSettings(parsedResponse));\r\n onSuccess();\r\n }\r\n});\r\n\r\nSettingsInitializer = connect(undefined, mapDispatchToProps)(SettingsInitializer);\r\n\r\nSettingsInitializer.propTypes = {\r\n onSuccess: PropTypes.func.isRequired\r\n};\r\n\r\n\r\n// Export\r\n// --------------------------------------------\r\nexport {SettingsInitializer}","\r\n// redux (action-types)\r\nimport {SAVE_SETTINGS} from 'redux/reducers/settings/app/action-types'\r\n\r\n\r\nconst saveSettings = (json) => ({type: SAVE_SETTINGS, json});\r\n\r\n\r\nexport {saveSettings}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// internal\r\nimport * as actions from './actions'\r\n\r\n\r\n// SessionLoader (not connected to store)\r\n// ---------------------------------------------\r\n\r\nclass SessionLoader extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tthis.props.loadUserSession();\r\n\t\tthis.props.loadClientSession();\r\n\t\tthis.props.loadClientSettings();\r\n\t\tthis.props.loadTokens();\r\n\t\tthis.props.onLoad();\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nSessionLoader.propTypes = {\r\n\tloadUserSession: PropTypes.func.isRequired,\r\n\tloadClientSession: PropTypes.func.isRequired,\r\n\tloadClientSettings: PropTypes.func.isRequired,\r\n\tloadTokens: PropTypes.func.isRequired,\r\n\tonLoad: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// SessionLoader (not connected to store)\r\n// ---------------------------------------------\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tloadUserSession: () => dispatch(actions.loadUserSession()),\r\n\tloadClientSession: () => dispatch(actions.loadClientSession()),\r\n\tloadClientSettings: () => dispatch(actions.loadClientSettings()),\r\n\tloadTokens: () => dispatch(actions.loadTokens())\r\n});\r\n\r\nSessionLoader = connect(undefined, mapDispatchToProps)(SessionLoader);\r\n\r\n\r\n// EXPORT\r\n// ---------------------------------------------\r\nexport {SessionLoader}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\nimport {getSessionData} from 'redux/reducers/selectors'\r\nimport {getUserSessionData, getClientSessionData, getTokenData} from 'redux/reducers/session/selectors'\r\n\r\n\r\n// SessionVerifier (not connected to store)\r\n// ------------------------------------------\r\n\r\nclass SessionVerifier extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tconst validSession = \r\n\t\t\tthis.props.user.size > 0 &&\r\n\t\t\tthis.props.client.size > 0 &&\r\n\t\t\tthis.props.tokens.size > 0;\r\n\r\n\t\tif (validSession) { this.props.onSuccess(); }\r\n\t\telse { this.props.onFail(); }\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nSessionVerifier.propTypes = {\r\n\tuser: PropTypes.object.isRequired,\r\n\tclient: PropTypes.object.isRequired,\r\n\ttokens: PropTypes.object.isRequired,\r\n\tonSuccess: PropTypes.func.isRequired,\r\n\tonFail: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// SessionVerifier (connected to store)\r\n// ------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\tuser: getUserSessionData(getSessionData(store)),\r\n\tclient: getClientSessionData(getSessionData(store)),\r\n\ttokens: getTokenData(getSessionData(store))\r\n});\r\n\r\nSessionVerifier = connect(mapStoreToProps)(SessionVerifier);\r\n\r\n\r\nexport {SessionVerifier}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {withRouter} from 'react-router-dom'\r\n\r\n\r\n// LoginUrlChecker (not connected to router)\r\n// ----------------------------------------------\r\n\r\nclass LoginUrlChecker extends React.Component {\r\n componentDidMount() {\r\n if (this.isLoginUrl()) {\r\n this.props.onLoginUrl();\r\n } else {\r\n this.props.onOtherUrl();\r\n }\r\n }\r\n\r\n isLoginUrl() {\r\n const {\r\n location: { pathname },\r\n } = this.props.history;\r\n return (\r\n pathname === \"/\" ||\r\n pathname.startsWith(\"/login\") ||\r\n pathname.startsWith(\"/user\") ||\r\n pathname.startsWith(\"/clients/\")\r\n );\r\n }\r\n\r\n render() {\r\n return null;\r\n }\r\n}\r\n\r\nLoginUrlChecker.propTypes = {\r\n\thistory: PropTypes.object.isRequired,\r\n\tonLoginUrl: PropTypes.func.isRequired,\r\n\tonOtherUrl: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// LoginUrlChecker (connected to router)\r\n// ----------------------------------------------\r\n\r\nLoginUrlChecker = withRouter(LoginUrlChecker);\r\n\r\n\r\n// EXPORT\r\n// ----------------------------------------------\r\n\r\nexport {LoginUrlChecker}","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { Redirect } from \"react-router-dom\";\r\n\r\n// redux (selectors)\r\nimport { getSessionData } from \"redux/reducers/selectors\";\r\nimport { getClientSessionData } from \"redux/reducers/session/selectors\";\r\nimport { getUrlToken } from \"redux/reducers/session/client/selectors\";\r\n\r\n// LoginUrlInitializer (not connected to store)\r\n// ----------------------------------------------\r\n\r\nclass LoginUrlInitializer extends React.Component {\r\n componentDidMount() {\r\n this.props.onFinish();\r\n }\r\n\r\n render() {\r\n const { pathname } = window.location;\r\n const page =\r\n pathname.indexOf(\"login\") !== -1 ||\r\n pathname.indexOf(\"user\") !== -1 ||\r\n pathname.indexOf(\"forgot\") !== -1 ||\r\n\t\t\tpathname.indexOf(\"proctorio\") !== -1 ||\r\n pathname.indexOf(\"reset\") !== -1\r\n ? pathname\r\n : \"/login\";\r\n\r\n return ;\r\n }\r\n}\r\n\r\nLoginUrlInitializer.propTypes = {\r\n urlToken: PropTypes.string.isRequired,\r\n onFinish: PropTypes.func.isRequired,\r\n};\r\n\r\n// LoginUrlInitializer (connected to store)\r\n// ----------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n urlToken: getUrlToken(getClientSessionData(getSessionData(store))),\r\n});\r\n\r\nLoginUrlInitializer = connect(mapStoreToProps)(LoginUrlInitializer);\r\n\r\nexport { LoginUrlInitializer };\r\n","\nimport {Machine} from 'xstate'\nimport {XStateConfig} from './xstate-config'\n\n\n// #########################################\n// STATE & EVENT NAMES\n// #########################################\n\nconst STATES = {\n INITIALIZING: 'initializing',\n INITIALIZE_ERROR: 'initialize_error',\n INITIALIZED: 'initialized'\n}\n\nconst INITIALIZING_STATES = {\n INITIALIZING_APP_SETTINGS: 'initializing_app_settings',\n VALIDATING_APP_SETTINGS: 'validating_app_settings',\n INITIALIZING_CLIENT: 'initializing_client',\n INITIALIZING_SESSION_MESSAGES: 'initializing_session_messages',\n INITIALIZING_GENERAL_MESSAGES: 'initializing_general_messages',\n PRELOADING_LOGOS: 'preloading_logos',\n NO_CLIENT: 'no_client_to_initialize',\n}\n\nconst VALIDATING_APP_SETTINGS_STATES = {\n CHECKING_BROWSER_SUPPORT: 'checking_browser_support',\n UNSUPPORTED_BROWSER: 'unsupported_browser_detected'\n}\n\nconst INITIALIZING_CLIENT_STATES = {\n CHECKING_LOGIN_ATTEMPT: 'checking_login_attempt',\n LOADING_SESSION: 'loading_session',\n VERIFYING_SESSION: 'verifying_session',\n INITIALIZING_SESSION: 'initializing_session',\n INITIALIZING_LOGIN_URL: 'initializing_login_url'\n}\n\nconst EVENTS = {\n ERROR: 'error',\n SUCCESS: 'success',\n LOGIN_ATTEMPT: 'login.attempt',\n VALID_SESSION: 'valid.session',\n SESSION_LOADED: 'session.loaded',\n INVALID_SESSION: 'invalid.session',\n NO_LOGIN_ATTEMPT: 'no.login.attempt',\n CONFIG_INITIALIZED: 'config.initialized',\n CLIENT_INITIALIZED: 'client.initialized',\n SESSION_INITIALIZED: 'session.initialized',\n INITIALIZE_FAILED: 'initialization.failed',\n MESSAGES_INITIALIZED: 'messages.initialized',\n SESSION_MESSAGES_INITALIZED: 'session.messages.initialized',\n DETECTED_UNSUPPORTED_BROWSER: 'detected.unsupported.browser'\n}\n\n\n// #########################################\n// INITIALIZING CLIENT STATES\n// #########################################\n\nconst checkingLoginAttempt = new XStateConfig();\ncheckingLoginAttempt.addTransition(EVENTS.NO_LOGIN_ATTEMPT, INITIALIZING_CLIENT_STATES.LOADING_SESSION);\ncheckingLoginAttempt.addTransition(EVENTS.LOGIN_ATTEMPT, INITIALIZING_CLIENT_STATES.INITIALIZING_SESSION);\n\nconst loadingSession = new XStateConfig();\nloadingSession.addTransition(EVENTS.SESSION_LOADED, INITIALIZING_CLIENT_STATES.VERIFYING_SESSION);\n\nconst verifyingSession = new XStateConfig();\nverifyingSession.addTransition(EVENTS.INVALID_SESSION, INITIALIZING_CLIENT_STATES.INITIALIZING_SESSION);\n\nconst initializingSession = new XStateConfig();\ninitializingSession.addTransition(EVENTS.SESSION_INITIALIZED, INITIALIZING_CLIENT_STATES.INITIALIZING_LOGIN_URL);\n\nconst initializingLoginUrl = new XStateConfig();\n\n\n// #########################################\n// VALIDATING APP SETTINGS STATES\n// #########################################\n\nconst checkingBrowserSupport = new XStateConfig();\ncheckingBrowserSupport.addTransition(EVENTS.DETECTED_UNSUPPORTED_BROWSER, VALIDATING_APP_SETTINGS_STATES.UNSUPPORTED_BROWSER);\n\nconst unsupportedBrowser = new XStateConfig();\n\n\n// #########################################\n// INITIALIZING STATES\n// #########################################\n\nconst initializingAppSettings = new XStateConfig();\ninitializingAppSettings.addTransition(EVENTS.CONFIG_INITIALIZED, INITIALIZING_STATES.VALIDATING_APP_SETTINGS);\n\nconst validatingAppSettings = new XStateConfig();\nvalidatingAppSettings.initialState = VALIDATING_APP_SETTINGS_STATES.CHECKING_BROWSER_SUPPORT;\nvalidatingAppSettings.addTransition(EVENTS.SUCCESS, INITIALIZING_STATES.INITIALIZING_CLIENT);\nvalidatingAppSettings.addState(VALIDATING_APP_SETTINGS_STATES.CHECKING_BROWSER_SUPPORT, checkingBrowserSupport);\nvalidatingAppSettings.addState(VALIDATING_APP_SETTINGS_STATES.UNSUPPORTED_BROWSER, unsupportedBrowser);\n\nconst initializingClient = new XStateConfig();\ninitializingClient.initialState = INITIALIZING_CLIENT_STATES.CHECKING_LOGIN_ATTEMPT;\ninitializingClient.addTransition(EVENTS.VALID_SESSION, INITIALIZING_STATES.INITIALIZING_SESSION_MESSAGES);\ninitializingClient.addTransition(EVENTS.CLIENT_INITIALIZED, INITIALIZING_STATES.INITIALIZING_GENERAL_MESSAGES);\ninitializingClient.addTransition(EVENTS.INITIALIZE_FAILED, INITIALIZING_STATES.NO_CLIENT);\ninitializingClient.addState(INITIALIZING_CLIENT_STATES.CHECKING_LOGIN_ATTEMPT, checkingLoginAttempt);\ninitializingClient.addState(INITIALIZING_CLIENT_STATES.LOADING_SESSION, loadingSession);\ninitializingClient.addState(INITIALIZING_CLIENT_STATES.VERIFYING_SESSION, verifyingSession);\ninitializingClient.addState(INITIALIZING_CLIENT_STATES.INITIALIZING_SESSION, initializingSession);\ninitializingClient.addState(INITIALIZING_CLIENT_STATES.INITIALIZING_LOGIN_URL, initializingLoginUrl);\n\nconst initializingSessionMessages = new XStateConfig();\ninitializingSessionMessages.addTransition(EVENTS.SESSION_MESSAGES_INITALIZED, INITIALIZING_STATES.INITIALIZING_GENERAL_MESSAGES);\n\nconst initializingGeneralMessages = new XStateConfig();\ninitializingGeneralMessages.addTransition(EVENTS.MESSAGES_INITIALIZED, INITIALIZING_STATES.PRELOADING_LOGOS);\n\nconst preloadingLogos = new XStateConfig();\nconst noClient = new XStateConfig();\n\n\n// #########################################\n// INIT STATES\n// #########################################\n\nconst initializing = new XStateConfig();\ninitializing.initialState = INITIALIZING_STATES.INITIALIZING_APP_SETTINGS;\ninitializing.addState(INITIALIZING_STATES.INITIALIZING_APP_SETTINGS, initializingAppSettings);\ninitializing.addState(INITIALIZING_STATES.VALIDATING_APP_SETTINGS, validatingAppSettings);\ninitializing.addState(INITIALIZING_STATES.INITIALIZING_CLIENT, initializingClient);\ninitializing.addState(INITIALIZING_STATES.INITIALIZING_SESSION_MESSAGES, initializingSessionMessages);\ninitializing.addState(INITIALIZING_STATES.INITIALIZING_GENERAL_MESSAGES, initializingGeneralMessages);\ninitializing.addState(INITIALIZING_STATES.PRELOADING_LOGOS, preloadingLogos);\ninitializing.addState(INITIALIZING_STATES.NO_CLIENT, noClient);\ninitializing.addTransition(EVENTS.MESSAGES_INITIALIZED, STATES.INITIALIZED); // temp\ninitializing.addTransition(EVENTS.SUCCESS, STATES.INITIALIZED);\ninitializing.addTransition(EVENTS.ERROR, STATES.INITIALIZE_ERROR);\n\nconst initialized = new XStateConfig();\nconst initializeError = new XStateConfig();\n\n\n// #########################################\n// INIT MACHINE\n// #########################################\n\nconst _init = new XStateConfig();\n_init.initialState = STATES.INITIALIZING;\n_init.addState(STATES.INITIALIZING, initializing);\n_init.addState(STATES.INITIALIZE_ERROR, initializeError);\n_init.addState(STATES.INITIALIZED, initialized);\n\nconst machine = Machine(_init.toObject());\nmachine.id = \"Init Machine\";\n\n\n// #########################################\n// EXPORT\n// #########################################\n\nconst init = {\n machine,\n EVENTS: {...EVENTS},\n STATES: {...STATES},\n INITIALIZING_STATES: {...INITIALIZING_STATES},\n INITIALIZING_CLIENT_STATES: {...INITIALIZING_CLIENT_STATES},\n VALIDATING_APP_SETTINGS_STATES: {...VALIDATING_APP_SETTINGS_STATES}\n}\n\nexport {init}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {SessionLoader} from './session-loader'\r\nimport {SessionVerifier} from './session-verifier'\r\nimport {LoginUrlChecker} from './login-url-checker'\r\nimport {SessionInitializer} from './session-initializer'\r\nimport {LoginUrlInitializer} from './login-url-initializer'\r\n\r\n// machines\r\nimport {init} from 'machines/init'\r\n\r\n\r\nconst {EVENTS, INITIALIZING_CLIENT_STATES:STATES} = init;\r\n\r\n\r\nconst InitializingClientView = () => (\r\n\t\r\n\t\t{{\r\n\t\t\t[STATES.CHECKING_LOGIN_ATTEMPT]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{(props) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t\t[STATES.LOADING_SESSION]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{({onLoad}) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t\t[STATES.VERIFYING_SESSION]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{(props) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t\t[STATES.INITIALIZING_SESSION]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{(props) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t\t[STATES.INITIALIZING_LOGIN_URL]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{({onFinish}) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t}}\r\n\t \r\n)\r\n\r\n\r\nexport {InitializingClientView}","\r\n// @xams-utils\r\nimport {check} from '@xams-utils/check-types'\r\n\r\n// @xams-entities\r\nimport {VersionRange} from '@xams-entities/version-range'\r\nimport {Version} from '@xams-entities/version'\r\n\r\n\r\nconst checkVersionSupport = (version, supportedVersion) =>\r\n{\r\n let include = false;\r\n let exclude = false;\r\n\r\n if (check.assigned(supportedVersion.include)) {\r\n include = checkVersionsSupported(version, supportedVersion.include);\r\n }\r\n if (check.assigned(supportedVersion.exclude)) {\r\n exclude = checkVersionsSupported(version, supportedVersion.exclude);\r\n }\r\n\r\n return include && !exclude;\r\n};\r\n\r\nconst checkVersionsSupported = (version, supportedVersions) =>\r\n{\r\n return supportedVersions.some(supportedVersion => {\r\n return isVersionSupported(version, supportedVersion);\r\n });\r\n};\r\n\r\n\r\nconst isVersionSupported = (version, supportedVersion) =>\r\n{\r\n if (Version.is(supportedVersion)) {\r\n return Version.equals(supportedVersion, version); \r\n }\r\n else if (VersionRange.is(supportedVersion)) {\r\n return VersionRange.contains(supportedVersion, version); \r\n }\r\n return false;\r\n};\r\n\r\n\r\nexport {checkVersionSupport}","\r\n// @xams-interactors\r\nimport {checkVersionSupport} from './check-version-support'\r\nimport {detectBrowser} from './detect-browser'\r\n\r\n// @xams-entities\r\nimport {Browser} from '@xams-entities/browser'\r\n\r\n\r\nconst checkBrowserSupport = (supportedBrowsers) => {\r\n const {browser, version} = detectBrowser();\r\n\r\n return Object.keys(supportedBrowsers).some(name => {\r\n const supportedBrowser = Browser.create(name);\r\n if (Browser.equals(browser, supportedBrowser)) {\r\n return checkVersionSupport(version, supportedBrowsers[name]);\r\n }\r\n return false;\r\n });\r\n};\r\n\r\n\r\nexport {checkBrowserSupport}\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// Redux\r\nimport {getAppSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getSettingsData} from 'redux/reducers/selectors'\r\nimport {getSupportedBrowsers} from 'redux/reducers/settings/app/selectors'\r\n\r\n// React\r\nimport {checkBrowserSupport} from './check-browser-support'\r\n\r\n\r\nclass BrowserChecker extends React.Component\r\n{\r\n componentDidMount()\r\n {\r\n if (this.isCurrentBrowserSupported()) {\r\n this.props.onSupported();\r\n }\r\n else {\r\n this.props.onNotSupported();\r\n }\r\n }\r\n\r\n isCurrentBrowserSupported()\r\n {\r\n const {supportedBrowsers} = this.props;\r\n\r\n return checkBrowserSupport(supportedBrowsers);\r\n };\r\n\r\n render()\r\n {\r\n return null;\r\n }\r\n}\r\n\r\n\r\nBrowserChecker.propTypes = {\r\n supportedBrowsers: PropTypes.object.isRequired,\r\n onSupported: PropTypes.func.isRequired,\r\n onNotSupported: PropTypes.func.isRequired\r\n};\r\n\r\n\r\nconst mapStoreToProps = store =>\r\n{\r\n const appSettingsData = getAppSettingsData(getSettingsData(store));\r\n return {\r\n supportedBrowsers: getSupportedBrowsers(appSettingsData)\r\n };\r\n};\r\n\r\nBrowserChecker = connect(mapStoreToProps)(BrowserChecker);\r\n\r\n\r\nexport {BrowserChecker}","const BROWSER = {\r\n AOL: 'aol',\r\n EDGE: 'edge',\r\n EDGE_CHROMIUM: 'edge-chromium',\r\n CHROME: 'chrome',\r\n FIREFOX: 'firefox',\r\n FXIOS: 'fxios',\r\n CRIOS: 'crios',\r\n IE: 'ie',\r\n SAFARI: 'safari',\r\n IOS : 'ios',\r\n};\r\n\r\nconst BROWSER_IMAGES = [\r\n BROWSER.CHROME,\r\n BROWSER.EDGE,\r\n BROWSER.EDGE_CHROMIUM,\r\n BROWSER.FIREFOX,\r\n BROWSER.FXIOS,\r\n BROWSER.CRIOS,\r\n BROWSER.OPERA,\r\n BROWSER.SAFARI,\r\n BROWSER.AOL,\r\n BROWSER.IE,\r\n BROWSER.IOS\r\n];\r\n\r\nexport {BROWSER, BROWSER_IMAGES}\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material design\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// constants\r\nimport {BROWSER_IMAGES} from './constants'\r\n\r\n// @xams-entities\r\nimport {Browser} from '@xams-entities/browser'\r\n\r\n// css\r\nimport './supported-browser.css'\r\n\r\n\r\nconst displayVersionsText = (versionsText, isIncludeText) => {\r\n if (!versionsText) {\r\n return null;\r\n }\r\n\r\n const startingText = isIncludeText ? '' : 'excluding ';\r\n \r\n return versionsText.map(text => (\r\n \r\n \r\n {`${startingText}${text}`}\r\n \r\n
\r\n ));\r\n}\r\n\r\nclass SupportedBrowser extends React.Component\r\n{\r\n render()\r\n {\r\n const {include, exclude} = this.props;\r\n \treturn (\r\n \r\n {this.image}\r\n
\r\n {this.heading}\r\n {displayVersionsText(include, true)}\r\n {displayVersionsText(exclude, false)}\r\n
\r\n
\r\n );\r\n }\r\n\r\n get image()\r\n {\r\n const {browser} = this.props;\r\n const {name} = browser;\r\n const imageIndex = BROWSER_IMAGES.indexOf(name);\r\n return imageIndex === -1 ?
:
;\r\n }\r\n\r\n get heading()\r\n {\r\n const {browser} = this.props;\r\n return (\r\n \t\r\n \r\n \t{Browser.toString(browser)}\r\n \r\n
\r\n )\r\n }\r\n}\r\n\r\n\r\nSupportedBrowser.propTypes = {\r\n browser: PropTypes.object.isRequired,\r\n include: PropTypes.arrayOf(\r\n PropTypes.object\r\n ),\r\n exclude: PropTypes.arrayOf(\r\n PropTypes.object\r\n ) \r\n};\r\n\r\n\r\nexport {SupportedBrowser}","\r\n// @xams-utils\r\nimport {check} from '@xams-utils/check-types'\r\n\r\n// @xams-entities\r\nimport {Browser} from '@xams-entities/browser'\r\nimport {Version} from '@xams-entities/version'\r\nimport {VersionRange} from '@xams-entities/version-range'\r\n\r\n\r\nconst sortSupportedBrowsers = (currentBrowser, supportedBrowsers) =>\r\n{\r\n return Object.keys(supportedBrowsers)\r\n .map(browser => {\r\n return getTextForSupportedBrowser(browser, supportedBrowsers[browser]);\r\n })\r\n .sort((a, b) => {\r\n if (Browser.equals(currentBrowser, a.browser)) {\r\n return -1;\r\n }\r\n else if (Browser.equals(currentBrowser, b.browser)) {\r\n return 1;\r\n }\r\n return a.browser.name.localeCompare(b.browser.name);\r\n });\r\n};\r\n\r\nconst getTextForSupportedBrowser = (browser, browserSupport) =>\r\n{\r\n const support = {browser: Browser.create(browser)};\r\n if (check.assigned(browserSupport.include)) {\r\n support.include = getVersionText(browserSupport.include);\r\n }\r\n if (check.assigned(browserSupport.exclude)) {\r\n support.exclude = getVersionText(browserSupport.exclude);\r\n }\r\n return support;\r\n};\r\n\r\nconst getVersionText = supportedVersions =>\r\n{\r\n const versionText = [];\r\n const singleVersions = [];\r\n\r\n supportedVersions.forEach(supportedVersion => {\r\n if (Version.is(supportedVersion)) {\r\n singleVersions.push(Version.toString(supportedVersion));\r\n }\r\n else {\r\n versionText.push(VersionRange.toString(supportedVersion));\r\n }\r\n });\r\n\r\n if (singleVersions.length > 0) {\r\n versionText.push(getSingleVersionsText(singleVersions));\r\n }\r\n\r\n return versionText;\r\n};\r\n\r\nconst getSingleVersionsText = singleVersions =>\r\n{\r\n let singleVersionsText = 'Version' + (singleVersions.length > 1 ? 's' : '');\r\n singleVersionsText += ' '+singleVersions.sort((a, b) => {\r\n return a.localeCompare(b);\r\n }).join(', ');\r\n return singleVersionsText;\r\n};\r\n\r\n\r\nexport {sortSupportedBrowsers}\r\n","\n// npm\nimport React from 'react'\nimport {connect} from 'react-redux'\n\n// react\nimport {Align } from 'components/layout/align'\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\nimport {MaterialText} from 'components/presentation/material-text'\nimport {SupportedBrowser} from './supported-browser'\nimport {sortSupportedBrowsers} from './sort-supported-browsers'\n\n// redux\nimport {getAppSettingsData} from 'redux/reducers/settings/selectors'\nimport {getSettingsData} from 'redux/reducers/selectors'\nimport {getSupportedBrowsers} from 'redux/reducers/settings/app/selectors'\n\n// @xams-interactor\nimport {detectBrowser} from '../browser_checker/detect-browser'\n\n// @xams-entities\nimport {Version} from '@xams-entities/version'\nimport {Browser} from '@xams-entities/browser'\n\n// css\nimport './page.css';\n\n\nconst browserListStyle = {\n overflow: 'hidden',\n float: 'none'\n};\n\nconst textStyle = {\n color: '#fff',\n 'padding-bottom': '20px'\n}\n\nconst displayCurrentBrowserText = (browser, version, currentBrowserSupported) => {\n const starting = currentBrowserSupported\n ? `Version, ${Version.toString(version)} , of your`\n : 'Your ';\n \n return (\n \n \n {`${starting} current ${Browser.toString(browser)} browser is not supported`}\n \n \n Listed below are the supported browsers and their versions\n \n \n );\n}\n\nconst displaySupportedBrowsers = (supportedBrowsers) => \n{\n const {browser, version} = detectBrowser(); \n const sortedSupportedBrowsers = sortSupportedBrowsers(browser, supportedBrowsers); \n const currentBrowserSupported = isCurrentBrowserSupported(browser, sortedSupportedBrowsers);\n \n return (\n \n
\n {displayCurrentBrowserText(browser, version, currentBrowserSupported)}\n
\n {sortedSupportedBrowsers.map((browser, index) => (\n
\n ))}\n
\n );\n};\n\nconst isCurrentBrowserSupported = (browser, browserList) =>\n{\n if (browserList.length === 0) {\n return false;\n }\n return Browser.equals(browser, browserList[0].browser);\n}\n\nclass UnsupportedBrowserPage extends React.Component \n{ \n render()\n {\n const {supportedBrowsers} = this.props;\n const {browser, version} = detectBrowser();\n const title = `Browser not supported: ${Browser.toString(browser)} (${Version.toString(version)})`\n\n return (\n \n \n {displaySupportedBrowsers(supportedBrowsers)} \n \n );\n }\n}\n\n\n// BrowserNotSupportedPage (connected to store)\n// ------------------------------------------------------------\n\nconst mapStoreToProps = store => ({\n supportedBrowsers: getSupportedBrowsers(getAppSettingsData(getSettingsData(store)))\n});\n\nUnsupportedBrowserPage = connect(mapStoreToProps)(UnsupportedBrowserPage);\n\n\nexport {UnsupportedBrowserPage}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {BrowserChecker} from './browser_checker'\r\nimport {UnsupportedBrowserPage} from './unsupported_browser'\r\n\r\n// machines\r\nimport {init} from 'machines/init'\r\n\r\n\r\nconst {EVENTS, VALIDATING_APP_SETTINGS_STATES:STATES} = init;\r\n\r\n\r\nconst ValidatingAppSettingsView = () => (\r\n\t\r\n\t\t{{\r\n\t\t\t[STATES.CHECKING_BROWSER_SUPPORT]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{(props) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t\t[STATES.UNSUPPORTED_BROWSER]: () => (\r\n\t\t\t\t \r\n\t\t\t)\r\n\t\t}}\r\n\t \r\n)\r\n\r\n\r\nexport {ValidatingAppSettingsView}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// xams-components\r\nimport {StateView, StateControl} from 'temp/xams.UI.Components/machine'\r\n\r\n// react\r\nimport {NoClientPage} from './no-client-page'\r\nimport {LogoPreloader} from './logo-preloader'\r\nimport {SettingsInitializer} from './settings-initializer'\r\nimport {InitializingClientView} from './initializing_client/view'\r\nimport {ValidatingAppSettingsView} from './validating_app_settings/view'\r\nimport {MessageLoader} from 'components/functional/message_loader/loader'\r\n\r\n// machines\r\nimport {init} from 'machines/init'\r\n\r\n\r\nconst {EVENTS, INITIALIZING_STATES:STATES} = init;\r\n\r\n\r\nconst InitializingView = () => (\r\n\t\r\n\t\t{{\r\n\t\t\t[STATES.INITIALIZING_APP_SETTINGS]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{(props) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t\t[STATES.VALIDATING_APP_SETTINGS]: () => (\r\n\t\t\t\tValidatingAppSettingsView()\r\n\t\t\t),\r\n\t\t\t[STATES.INITIALIZING_CLIENT]: () => (\r\n\t\t\t\tInitializingClientView()\r\n\t\t\t),\r\n\t\t\t[STATES.INITIALIZING_SESSION_MESSAGES]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{(props) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t\t[STATES.INITIALIZING_GENERAL_MESSAGES]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{(props) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t\t[STATES.PRELOADING_LOGOS]: () => (\r\n\t\t\t\t\r\n\t\t\t\t\t{(props) => }\r\n\t\t\t\t \r\n\t\t\t),\r\n\t\t\t[STATES.NO_CLIENT]: () => (\r\n\t\t\t\t \r\n\t\t\t)\r\n\t\t}}\r\n\t \r\n)\r\n\r\n\r\nexport {InitializingView}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// xams-components-functional\r\nimport {StateMachine, StateView} from 'temp/xams.UI.Components/machine'\r\nimport {Callback} from '@xams-components-functional/callback'\r\n\r\n// react\r\nimport {InitializingView} from './initializing/view'\r\nimport {SetNetworkError} from 'components/pages/network_error/set-network-error'\r\n\r\n// redux (actions)\r\nimport {setAppInitialized} from './actions'\r\n\r\n// machines\r\nimport {init} from 'machines/init'\r\n\r\n\r\nconst {STATES} = init;\r\n\r\n\r\n// NOT CONNECTED TO STORE\r\n// ---------------------------------------------------------------------\r\n\r\nlet AppInitializationMachine = ({setAppInitialized}) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{{\r\n\t\t\t\t\t[STATES.INITIALIZING]: () => (\r\n\t\t\t\t\t\tInitializingView()\r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.INITIALIZE_ERROR]: () => (\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t),\r\n\t\t\t\t\t[STATES.INITIALIZED]: () => (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t)\r\n\t\t\t\t}}\r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\nAppInitializationMachine.propTypes = {\r\n\tsetAppInitialized: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// CONNECTED TO STORE\r\n// ---------------------------------------------------------------------\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tsetAppInitialized: () => dispatch(setAppInitialized())\r\n});\r\n\r\nAppInitializationMachine = connect(undefined, mapDispatchToProps)(AppInitializationMachine);\r\n\r\n\r\n// EXPORT\r\n// ---------------------------------------------------------------------\r\nexport {AppInitializationMachine}","\r\n// redux (action-types)\r\nimport {INITIALIZED} from 'redux/reducers/app/action-types'\r\n\r\n\r\nconst setAppInitialized = () => ({\r\n\ttype: INITIALIZED\r\n});\r\n\r\n\r\nexport {setAppInitialized}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// react\r\nimport {MaterialText} from './material-text'\r\n\r\n\r\nclass LoadingText extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\t\tthis.state = {dotCount: props.maxDots};\r\n\r\n\t\tthis.updateDotCount = () => {\r\n\t\t\tconst {dotCount} = this.state;\r\n\t\t\tconst {minDots, maxDots} = this.props;\r\n\t\t\tthis.setState({dotCount: dotCount === maxDots ? minDots : dotCount + 1});\r\n\t\t\tthis.timeoutId = setTimeout(this.updateDotCount, this.props.interval);\r\n\t\t}\r\n\r\n\t\tthis.timeoutId = setTimeout(this.updateDotCount, props.interval);\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tclearTimeout(this.timeoutId);\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tconst text = `${this.props.children}${'.'.repeat(this.state.dotCount)}`;\r\n\t\treturn {text} ;\r\n\t}\r\n\r\n\tget MaterialTextProps()\r\n\t{\r\n\t\tconst {children, minDots, maxDots, interval, ...materialTextProps} = this.props;\r\n\t\treturn materialTextProps;\r\n\t}\r\n}\r\n\r\nLoadingText.propTypes = {\r\n\tchildren: PropTypes.string.isRequired,\r\n\tminDots: PropTypes.number.isRequired,\r\n\tmaxDots: PropTypes.number.isRequired,\r\n\tinterval: PropTypes.number.isRequired\r\n}\r\n\r\nLoadingText.defaultProps = {\r\n\ttext: '',\r\n\tminDots: 0,\r\n\tmaxDots: 3,\r\n\tinterval: 250\r\n}\r\n\r\n\r\nexport {LoadingText}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// react\r\nimport {AppInitializationMachine} from './machine'\r\nimport {LoadingText} from 'components/presentation/loading-text'\r\nimport {AppPageModifier} from 'components/pages/app-page-modifier'\r\n\r\n\r\nconst AppInitializationPage = () =>\r\n{\r\n\tconst appBarProps = {\r\n\t\ttitle: \"Initializing your app\",\r\n\t\tloadingTitle: true,\r\n\t\tlogo: false\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {AppInitializationPage}","\n// npm\nimport React from 'react'\nimport PropTypes from 'prop-types'\nimport {connect} from 'react-redux'\nimport {Switch, Route, withRouter} from 'react-router-dom'\n\n// react\nimport {LoginPage} from './login/page'\nimport {ForgotPasswordPage} from './forgot_password/page'\nimport {ProctorioMachinePage} from './proctorio/machine-page'\nimport {ProctorioStartPage} from './proctorio/start-page'\nimport {ProctorioFinishPage} from './proctorio/end/finish-page'\nimport {ProctorioReturnPage} from './proctorio/return/return-page'\nimport {NetworkErrorPage} from './network_error/page'\nimport {SessionPageWrapper} from './session-page-wrapper'\nimport {AppInitializationPage} from './app_initialization/page'\n\n// redux (selectors)\nimport {getAppData} from 'redux/reducers/selectors'\nimport {isInitialized, hasNetworkErrorOccurred} from 'redux/reducers/app/selectors'\n\n// utils\nimport {routingBlocker, BLOCKING_TYPES} from 'utils/routing-blocker'\n\n// messages\nimport {withMessages} from 'components/hocs/messages'\nimport {MESSAGE_IDS} from 'constants/message-ids'\n\n\n// PageRouter (not connected to store/router)\n// ------------------------------------------------------\n\nclass PageRouter extends React.Component\n{\n\tconstructor(props)\n\t{\n\t\tsuper(props);\n\n\t\tthis.unsubscribe = routingBlocker.subscribe((location, action) => {\n\t\t\tif (action === \"PUSH\" || action === \"REPLACE\") { return; }\n\n\t\t\tconst atLogin = this.props.location.pathname.includes('login');\n\t\t\tconst toNonLogin = !location.pathname.includes('login');\n\n\t\t\tif (atLogin && toNonLogin) {\n\t\t\t\tconst {messages} = this.props;\n\t\t\t\t\n\t\t\t\treturn {\n\t\t\t\t\ttype: BLOCKING_TYPES.BLOCK,\n\t\t\t\t\tmessage: messages[MESSAGE_IDS.GENERAL.BROWSER_NAV_INTERRUPTED]\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\trender()\n\t{\n\t\tconst {initialized, networkError, location} = this.props;\n\n\t\tif (networkError) { return ; }\n\t\tif (!initialized) { return ; }\n\n\t\treturn (\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t{/* */}\n\t\t\t\t\n\t\t\t \n\t\t)\n\t}\n\n\tcomponentWillUnmount()\n\t{\n\t\tthis.unsubscribe();\n\t}\n}\n\nPageRouter.propTypes = {\n\tinitialized: PropTypes.bool.isRequired,\n\tnetworkError: PropTypes.bool.isRequired,\n\thistory: PropTypes.object.isRequired,\n\tlocation: PropTypes.object.isRequired\n}\n\n\n// PageRouter (not connected to store/router)\n// ------------------------------------------------------\n\nconst mapStoreToProps = (store) => ({\n\tinitialized: isInitialized(getAppData(store)),\n\tnetworkError: hasNetworkErrorOccurred(getAppData(store))\n})\n\nPageRouter = connect(mapStoreToProps)(PageRouter);\nPageRouter = withRouter(PageRouter);\nPageRouter = withMessages(PageRouter);\n\n\n// EXPORT\n// ------------------------------------------------------\nexport {PageRouter}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// react\r\nimport {Image} from 'components/presentation/image'\r\n\r\n// redux (selectors)\r\nimport {getSettingsData} from 'redux/reducers/selectors'\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getAppBarLogoUrl} from 'redux/reducers/settings/client/selectors'\r\n\r\n\r\n// AppBarLogo (not connected to store)\r\n// ----------------------------------------------------------\r\n\r\nlet AppBarLogo = (props) => (\r\n\t \r\n);\r\n\r\nAppBarLogo.propTypes = {\r\n\tlogoUrl: PropTypes.string.isRequired\r\n}\r\n\r\n\r\n// AppBarLogo (connected to store)\r\n// ----------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\tlogoUrl: getAppBarLogoUrl(getClientSettingsData(getSettingsData(store)))\r\n});\r\n\r\nAppBarLogo = connect(mapStoreToProps)(AppBarLogo);\r\n\r\n\r\n// Export\r\n// ----------------------------------------------------------\r\nexport {AppBarLogo}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Select from '@material-ui/core/Select'\r\nimport MenuItem from '@material-ui/core/MenuItem'\r\n\r\n// utils\r\nimport {findUserGuidsByQuery} from '../query-logs'\r\nimport {localStorageApi, KEYS} from 'libs/browser_storage/local-storage-api'\r\n\r\n\r\nclass UserSelector extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {value: null}\r\n\r\n\t\tthis.update = ({target:{value}}) => {\r\n\t\t\tthis.setState({value});\r\n\t\t\tthis.props.onChange(value);\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.Options}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget Options()\r\n\t{\r\n\t\tconst options = [this.NullOption];\r\n\t\tconst users = localStorageApi.retrieveDataFrom(KEYS.USERS);\r\n\r\n\t\tconst getUserName = (guid) => {\r\n\t\t\tconst user = users[guid];\r\n\t\t\treturn user ? user.name : guid;\r\n\t\t}\r\n\r\n\t\tconst userGuids = findUserGuidsByQuery();\r\n\t\tuserGuids.forEach((userGuid, index) => {\r\n\t\t\tconst key = this.createKey(userGuid);\r\n\t\t\tconst userName = getUserName(userGuid);\r\n\t\t\toptions.push({userName} );\r\n\t\t});\r\n\r\n\t\treturn options;\r\n\t}\r\n\r\n\tget NullOption()\r\n\t{\r\n\t\tconst key = this.createKey(null);\r\n\t\treturn {\"Not specified\"} ;\r\n\t}\r\n\r\n\tcreateKey(userGuid)\r\n\t{\r\n\t\treturn `user-selector-${userGuid}`;\r\n\t}\r\n}\r\n\r\nUserSelector.propTypes = {\r\n\tonChange: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nexport {UserSelector}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Select from '@material-ui/core/Select'\r\nimport MenuItem from '@material-ui/core/MenuItem'\r\n\r\n// utils\r\nimport {findExamGuidsByQuery} from '../query-logs'\r\nimport {localStorageApi, KEYS} from 'libs/browser_storage/local-storage-api'\r\n\r\n\r\nclass ExamSelector extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {value: null}\r\n\r\n\t\tthis.update = ({target:{value}}) => {\r\n\t\t\tthis.setState({value});\r\n\t\t\tthis.props.onChange(value);\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.Options}\r\n\t\t\t \r\n\t\t);\r\n\t}\r\n\r\n\tget Options()\r\n\t{\r\n\t\tconst options = [this.NullOption];\r\n\t\tconst exams = localStorageApi.retrieveDataFrom(KEYS.EXAMS);\r\n\r\n\t\tconst getExamName = (guid) => {\r\n\t\t\tconst exam = exams[guid];\r\n\t\t\treturn exam ? exam.name : guid;\r\n\t\t}\r\n\r\n\t\tconst examGuids = findExamGuidsByQuery({userGuid: this.props.userGuid});\r\n\t\texamGuids.forEach(examGuid => {\r\n\t\t\tconst key = this.createKey(examGuid);\r\n\t\t\tconst examName = getExamName(examGuid);\r\n\t\t\toptions.push({examName} );\r\n\t\t});\r\n\r\n\t\treturn options;\r\n\t}\r\n\r\n\tget NullOption()\r\n\t{\r\n\t\tconst key = this.createKey(null);\r\n\t\treturn {\"Not specified\"} ;\r\n\t}\r\n\r\n\tcreateKey(examGuid)\r\n\t{\r\n\t\treturn `exam-selector-${examGuid}`;\r\n\t}\r\n}\r\n\r\nExamSelector.propTypes = {\r\n\tuserGuid: PropTypes.string.isRequired,\r\n\tonChange: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nexport {ExamSelector}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Input from '@material-ui/core/Input'\r\n\r\n\r\nconst getDateTime = (value) =>\r\n{\r\n\tif (!value) { return ''; }\r\n\r\n\tconst isDate = value instanceof Date;\r\n\tconst dateTime = isDate ? value : new Date(value);\r\n\tconst dateTimeString = dateTime.toISOString();\r\n\r\n\tconst colonIndex = dateTimeString.lastIndexOf(':');\r\n\treturn dateTimeString.slice(0, colonIndex);\r\n}\r\n\r\nconst DateTimeSelector = (props) =>\r\n{\r\n\tconst currentDate = getDateTime(props.value);\r\n\r\n\tconst onChange = ({target:{value}}) => {\r\n\t\tprops.onChange(new Date(value));\r\n\t}\r\n\r\n\treturn ;\r\n}\r\n\r\nDateTimeSelector.propTypes = {\r\n\tonChange: PropTypes.func.isRequired,\r\n\tvalue: PropTypes.oneOfType([\r\n\t\tPropTypes.instanceOf(Date),\r\n\t\tPropTypes.string,\r\n\t\tPropTypes.number\r\n\t])\r\n}\r\n\r\n\r\nexport {DateTimeSelector}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Table from '@material-ui/core/Table'\r\nimport TableRow from '@material-ui/core/TableRow'\r\nimport TableCell from '@material-ui/core/TableCell'\r\n\r\n// react\r\nimport {UserSelector} from './user-selector'\r\nimport {ExamSelector} from './exam-selector'\r\nimport {DateTimeSelector} from './date-time-selector'\r\nimport {MaterialText} from 'components/presentation/material-text'\r\nimport {MultiLineTextEditor} from 'components/presentation/text_editors/multi_line'\r\n\r\n\r\nclass BugReportForm extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {\r\n\t\t\tuserGuid: null,\r\n\t\t\texamGuid: undefined,\r\n\t\t\tminDt: null,\r\n\t\t\tmaxDt: null,\r\n\t\t\tbugDescription: \"\"\r\n\t\t}\r\n\r\n\t\tthis.maxDate = new Date();\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.setUserGuid = (userGuid) => {\r\n\t\t\tconst examGuid = userGuid === null ? undefined : null;\r\n\t\t\tthis.setState({userGuid, examGuid});\r\n\t\t}\r\n\r\n\t\tthis.setExamGuid = (examGuid) => {\r\n\t\t\tthis.setState({examGuid});\r\n\t\t}\r\n\r\n\t\tconst isValidDateRange = (minDate, maxDate) => {\r\n\t\t\tif (!minDate || !maxDate) { return true; }\r\n\t\t\tconst minDateTime = minDate.getTime();\r\n\t\t\tconst maxDateTime = maxDate.getTime();\r\n\t\t\treturn maxDateTime >= minDateTime;\r\n\t\t}\r\n\r\n\t\tconst validateDateRange = (dateKey, date) => {\r\n\t\t\tconst isMinDt = dateKey === 'minDt';\r\n\t\t\tconst minDt = isMinDt ? date : this.state.minDt;\r\n\t\t\tconst maxDt = isMinDt ? this.state.maxDt : date;\r\n\t\t\tif (!isValidDateRange(minDt, maxDt)) {\r\n\t\t\t\talert(\"Bug end time can't happen before start time\");\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\tconst validateDate = (dateKey, date) => {\r\n\t\t\tif (!date) { return true; }\r\n\r\n\t\t\tconst dateTime = date && date.getTime();\r\n\t\t\tconst maxDateTime = this.maxDate.getTime();\r\n\r\n\t\t\tif (dateTime > maxDateTime) {\r\n\t\t\t\talert(\"Bug time can't be later than now\");\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\tconst setDt = (dateKey) => (dt) => {\r\n\t\t\tif (!validateDate(dateKey, dt)) { dt = null; }\r\n\t\t\telse if (!validateDateRange(dateKey, dt)) { dt = null; }\r\n\t\t\tthis.setState({[dateKey]: dt});\r\n\t\t}\r\n\r\n\t\tthis.setMinDt = setDt('minDt');\r\n\t\tthis.setMaxDt = setDt('maxDt');\r\n\r\n\t\tthis.setBugDescription = ({target:{value}}) => {\r\n\t\t\tthis.setState({bugDescription: value});\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tconst divStyle = {\r\n\t\t\tdisplay: 'flex',\r\n\t\t\tminWidth: 500,\r\n\t\t\tflexDirection: 'column',\r\n\t\t\talignItems: 'stretch'\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t{this.getRow('Which user did this happen to?', )}\r\n\t\t\t\t{this.ExamSelectorRow}\r\n\t\t\t\t{this.getDateTimeSelectorRow('min')}\r\n\t\t\t\t{this.getDateTimeSelectorRow('max')}\r\n\t\t\t\t{this.getRow(\"What was the bug?\", )}\r\n\t\t\t
\r\n\t\t)\r\n\t}\r\n\r\n\tcomponentDidUpdate()\r\n\t{\r\n\t\tthis.props.onChange(this.state, this.state.bugDescription !== \"\");\r\n\t}\r\n\r\n\tget ExamSelectorRow()\r\n\t{\r\n\t\tif (this.state.examGuid === undefined) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tconst {userGuid} = this.state;\r\n\t\tconst node = \r\n\t\treturn this.getRow('Exam', node);\r\n\t}\r\n\r\n\tgetDateTimeSelectorRow(type)\r\n\t{\r\n\t\tconst label = `Approximate bug ${type === 'min' ? 'start' : 'end'} time`;\r\n\t\tconst onChange = this[type === 'min' ? 'setMinDt' : 'setMaxDt'];\r\n\t\tconst value = this.state[type === 'min' ? 'minDt' : 'maxDt'];\r\n\t\treturn this.getRow(label, );\r\n\t}\r\n\r\n\tgetRow(label, node)\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\t{label} \r\n\t\t\t\t \r\n\t\t\t\t\r\n\t\t\t\t\t{node}\r\n\t\t\t\t \r\n\t\t\t \r\n\t\t)\r\n\t}\r\n}\r\n\r\nBugReportForm.propTypes = {\r\n\tonChange: PropTypes.func.isRequired\r\n}\r\n\r\n\r\nexport {BugReportForm}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Button from '@material-ui/core/Button'\r\n\r\n\r\nconst SubmitButton = ({onClick, enabled}) =>\r\n{\r\n\tconst props = {\r\n\t\tsize: 'small',\r\n\t\tcolor: 'primary',\r\n\t\tdisabled: !enabled,\r\n\t\tvariant: 'outlined',\r\n\t\tdisableRipple: true,\r\n\t\tdisableFocusRipple: true,\r\n\t\tonClick\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{\"Submit\"}\r\n\t\t \r\n\t);\r\n}\r\n\r\nSubmitButton.propTypes = {\r\n\tonClick: PropTypes.func.isRequired,\r\n\tenabled: PropTypes.bool.isRequired\r\n}\r\n\r\n\r\nexport {SubmitButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Button from '@material-ui/core/Button'\r\n\r\n\r\nconst CancelButton = ({onClick}) =>\r\n{\r\n\tconst props = {\r\n\t\tsize: 'small',\r\n\t\tcolor: 'secondary',\r\n\t\tvariant: 'outlined',\r\n\t\tdisableRipple: true,\r\n\t\tdisableFocusRipple: true,\r\n\t\tonClick\r\n\t}\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{\"Cancel\"}\r\n\t\t \r\n\t);\r\n}\r\n\r\n\r\nexport {CancelButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// material-ui\r\nimport Button from '@material-ui/core/Button'\r\n\r\n// react\r\nimport {BugReportForm} from './form'\r\nimport {SubmitButton} from './submit-button'\r\nimport {CancelButton} from './cancel-button'\r\nimport {Popup} from 'components/layout/popup'\r\n\r\n// redux (selectors)\r\nimport {getSessionData} from 'redux/reducers/selectors'\r\nimport {getUserSessionData} from 'redux/reducers/session/selectors'\r\nimport {getUserGuid} from 'redux/reducers/session/user/selectors'\r\n\r\n// utils\r\nimport {findUserActivityByQuery} from './query-logs'\r\nimport {errorApi} from 'libs/api/interface/api-error'\r\n\r\n\r\nconst title = \"Bug Submission Form\";\r\n\r\n\r\nclass BugReportPopup extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {\r\n\t\t\tcanSubmit: false\r\n\t\t}\r\n\r\n\t\tthis.initializeBoundMethods();\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tlet formData = {};\r\n\r\n\t\tthis.updateFormData = (formValues, valid) => {\r\n\t\t\tformData = {...formValues};\r\n\r\n\t\t\tif (valid !== this.state.canSubmit) {\r\n\t\t\t\tthis.setState({canSubmit: valid});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.submitForm = () => {\r\n\t\t\tconst payload = {\r\n\t\t\t\treporterGuid: this.props.reporterGuid,\r\n\t\t\t\tbugDescription: formData.bugDescription,\r\n\t\t\t\tuserActivity: findUserActivityByQuery(formData)\r\n\t\t\t};\r\n\r\n\t\t\terrorApi.sendBugReport(payload)\r\n\t\t\t.then(() => {\r\n\t\t\t\talert(\"Your bug has been reported! We will investigate it shortly\");\r\n\t\t\t\tthis.props.onClose();\r\n\t\t\t})\r\n\t\t\t.catch(() => {\r\n\t\t\t\talert(\"Bug submission failed. Please try again in a few minutes\");\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tconst popupProps = {\r\n\t\t\ttitle,\r\n\t\t\tcanClickBackdrop: true,\r\n\t\t\tcontent: {node: },\r\n\t\t\tbuttons: [this.SubmitButton, ]\r\n\t\t}\r\n\r\n\t\treturn ;\r\n\t}\r\n\r\n\tget SubmitButton()\r\n\t{\r\n\t\tconst props = {\r\n\t\t\tenabled: this.state.canSubmit,\r\n\t\t\tonClick: this.submitForm\r\n\t\t}\r\n\r\n\t\treturn ;\r\n\t}\r\n}\r\n\r\nBugReportPopup.propTypes = {\r\n\tonClose: PropTypes.func.isRequired,\r\n\treporterGuid: PropTypes.string.isRequired\r\n}\r\n\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\tconst userSessionData = getUserSessionData(getSessionData(store));\r\n\treturn {reporterGuid: getUserGuid(userSessionData)}\r\n}\r\n\r\nBugReportPopup = connect(mapStoreToProps)(BugReportPopup);\r\n\r\n\r\nexport {BugReportPopup}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// material-ui\r\nimport IconButton from '@material-ui/core/IconButton'\r\nimport BugReportIcon from '@material-ui/icons/BugReport'\r\n\r\n\r\nconst BugReportButton = ({onClick}) =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t \r\n\t);\r\n}\r\n\r\n\r\nexport {BugReportButton}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// react\r\nimport {BugReportPopup} from './popup'\r\nimport {BugReportButton} from './button'\r\n\r\n// redux (selectors)\r\nimport {getSessionData} from 'redux/reducers/selectors'\r\nimport {getUserSessionData} from 'redux/reducers/session/selectors'\r\nimport {isAdmin, getUserGuid, getName} from 'redux/reducers/session/user/selectors'\r\n\r\n\r\nclass BugReporter extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.state = {\r\n\t\t\tshow: false\r\n\t\t}\r\n\r\n\t\tthis.openBugReportForm = () => {\r\n\t\t\tthis.setState({show: true});\r\n\t\t}\r\n\r\n\t\tthis.closeBugReportForm = () => {\r\n\t\t\tthis.setState({show: false});\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\tif (!this.props.admin) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t{this.state.show && this.BugReportPopup}\r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tget BugReportPopup()\r\n\t{\r\n\t\treturn ;\r\n\t}\r\n}\r\n\r\nBugReporter.propTypes = {\r\n\tadmin: PropTypes.bool,\r\n\tuserGuid: PropTypes.string,\r\n\tuserName: PropTypes.string\r\n}\r\n\r\n\r\nconst mapStoreToProps = (store) =>\r\n{\r\n\tconst userSessionData = getUserSessionData(getSessionData(store));\r\n\r\n\treturn {\r\n\t\tadmin: isAdmin(userSessionData),\r\n\t\tuserGuid: getUserGuid(userSessionData),\r\n\t\tuserName: getName(userSessionData)\r\n\t}\r\n}\r\n\r\nBugReporter = connect(mapStoreToProps)(BugReporter);\r\n\r\n\r\nexport {BugReporter}","import React, { Component } from 'react';\r\nimport {withRouter} from 'react-router-dom'\r\nimport {connect} from 'react-redux'\r\n\r\nimport {getSessionData} from 'redux/reducers/selectors'\r\nimport {getUserSessionData} from 'redux/reducers/session/selectors'\r\nimport {getUserGuid, isAdmin} from 'redux/reducers/session/user/selectors'\r\n\r\nimport IconButton from '@material-ui/core/IconButton'\r\nimport AssessmentIcon from '@material-ui/icons/Assessment'\r\n\r\nclass ActivityReporter extends Component {\r\n state = { }\r\n\r\n constructor(props){\r\n super(props);\r\n\r\n this.handleClick = this.handleClick.bind(this);\r\n }\r\n\r\n handleClick() {\r\n const { history } = this.props;\r\n history.replace(\"/logs\");\r\n }\r\n\r\n render() {\r\n const {admin, userGuid, isOn} = this.props;\r\n\r\n if (!isOn || !userGuid || !admin) {\r\n\t\t\treturn null;\r\n }\r\n \r\n return ( \r\n \r\n\t\t\t \r\n\t\t \r\n );\r\n }\r\n}\r\n \r\nconst mapStoreToProps = (store) =>\r\n{\r\n const userSessionData = getUserSessionData(getSessionData(store));\r\n\r\n\treturn {\r\n isOn: true,\r\n admin: isAdmin(userSessionData),\r\n userGuid: getUserGuid(userSessionData)\r\n\t}\r\n}\r\n\r\nActivityReporter = connect(mapStoreToProps)(ActivityReporter);\r\nActivityReporter = withRouter(ActivityReporter);\r\n\r\n\r\nexport {ActivityReporter}","import React, { Component } from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\nimport { getProctorioData } from \"redux/reducers/selectors\";\r\nimport { getParent, getDebugMode } from \"redux/reducers/proctorio/selectors\";\r\n\r\nimport Avatar from \"@material-ui/core/Avatar\";\r\n\r\nclass ProctoringIndicator extends Component {\r\n render() {\r\n const { display } = this.props;\r\n\r\n return display ? P : null;\r\n }\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n const proctorioData = getProctorioData(store);\r\n const proctorioActive = getParent(proctorioData);\r\n const debugMode = getDebugMode(proctorioData)\r\n\r\n return {\r\n display: proctorioActive && debugMode\r\n };\r\n};\r\n\r\nProctoringIndicator = connect(mapStoreToProps)(ProctoringIndicator);\r\n\r\nexport { ProctoringIndicator };\r\n","// npm\r\nimport React from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { withRouter } from \"react-router-dom\";\r\n\r\n// material-ui\r\nimport Button from \"@material-ui/core/Button\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\nimport ExitToAppIcon from \"@material-ui/icons/ExitToApp\";\r\nimport Hidden from \"@material-ui/core/Hidden\";\r\nimport IconButton from \"@material-ui/core/IconButton\";\r\n\r\n// react\r\nimport { withMessages } from \"components/hocs/messages\";\r\nimport { MaterialText } from \"components/presentation/material-text\";\r\n\r\n// redux (actions)\r\nimport { getRemoveCurrentSessionActions } from \"components/actions\";\r\n\r\n// redux (selectors)\r\nimport { getSessionData } from \"redux/reducers/selectors\";\r\nimport { getUserGuid } from \"redux/reducers/session/user/selectors\";\r\nimport { getUrlToken } from \"redux/reducers/session/client/selectors\";\r\nimport {\r\n getClientSessionData,\r\n getUserSessionData,\r\n} from \"redux/reducers/session/selectors\";\r\n\r\n// libs\r\nimport { authenticationApi } from \"libs/api/interface/api-authentication\";\r\n\r\nimport { getLogoutPage } from \"components/pages/login/form/login_token.js\";\r\n\r\n// constants\r\nimport { MESSAGE_IDS } from \"constants/message-ids\";\r\n\r\nconst logOffMessageId = MESSAGE_IDS.GENERAL.LOG_OFF;\r\n\r\n// LogoutButton (not connected)\r\n// ------------------------------------------------------------------------\r\n\r\nlet LogoutButton = ({ onClick, classes, messages }) => {\r\n\tconst buttonProps = {\r\n className: classes.button,\r\n onClick,\r\n\t}\r\n const iconButtonProps = {\r\n ariaLabel: messages[logOffMessageId],\r\n component: \"span\",\r\n\t\t...buttonProps\r\n\t};\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n {messages[logOffMessageId]}\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n );\r\n};\r\n\r\nLogoutButton.propTypes = {\r\n onClick: PropTypes.func.isRequired,\r\n classes: PropTypes.object.isRequired,\r\n messages: PropTypes.shape({\r\n [logOffMessageId]: PropTypes.string.isRequired,\r\n }),\r\n};\r\n\r\n// LogoutButton (connected to store)\r\n// ------------------------------------------------------------------------\t\t\t\t// these connectors are ordered for increased performance\r\n\r\nconst mapStoreToProps = (store) => ({ store });\r\nconst mapDispatchToProps = (dispatch) => ({ dispatch });\r\n\r\nconst mergeProps = ({ store }, { dispatch }, { history, ...ownProps }) => {\r\n const removeSessionFromRedux = () => {\r\n getRemoveCurrentSessionActions().forEach((action) => dispatch(action));\r\n };\r\n\r\n const notifyServerAboutLogout = (sessionData) => {\r\n const userGuid = getUserGuid(getUserSessionData(sessionData));\r\n authenticationApi.logoutUser(userGuid);\r\n };\r\n\r\n const redirectToLoginPage = (sessionData) => {\r\n const { url, logout } = getLogoutPage();\r\n const urlToken = getUrlToken(getClientSessionData(sessionData));\r\n const logoutUrl = `${url}?${urlToken}`;\r\n\r\n if (logout) history.push(logoutUrl);\r\n else window.location.replace(logoutUrl);\r\n };\r\n\r\n return {\r\n ...ownProps,\r\n onClick: () => {\r\n const sessionData = getSessionData(store);\r\n notifyServerAboutLogout(sessionData);\r\n removeSessionFromRedux();\r\n redirectToLoginPage(sessionData);\r\n },\r\n };\r\n};\r\n\r\nLogoutButton = connect(\r\n mapStoreToProps,\r\n mapDispatchToProps,\r\n mergeProps\r\n)(LogoutButton);\r\n\r\n// LogoutButton (connected to router)\r\n// ------------------------------------------------------------------------\r\n\r\nLogoutButton = withRouter(LogoutButton);\r\n\r\n// LogoutButton (connected to messages)\r\n// ------------------------------------------------------------------------\r\n\r\nLogoutButton = withMessages(LogoutButton);\r\n\r\n// LogoutButton (connected to styles)\r\n// ---------------------------------------------------\r\n\r\nconst styles = ({ palette: { primary }, breakpoints, spacing }) => ({\r\n button: {\r\n backgroundColor: primary.dark,\r\n [breakpoints.down(\"xs\")]: {\r\n\t\t\tcolor: primary.contrastText,\r\n\t\t\tpadding: 8\r\n },\r\n },\r\n text: {\r\n color: primary.contrastText,\r\n },\r\n});\r\n\r\nLogoutButton = withStyles(styles)(LogoutButton);\r\n\r\n// Export\r\n// ---------------------------------------------------\r\nexport { LogoutButton };\r\n","const getTheme = (themeData) => {return themeData};\r\n\r\nexport {getTheme}\r\n","import React from \"react\";\r\n\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nconst styles = ({ spacing, palette }) => ({\r\n root: {\r\n display: \"flex\",\r\n \"&>div\": {\r\n display: \"flex\",\r\n boxSizing: \"border-box\",\r\n },\r\n \"&>div:not(:last-child)\": {\r\n width: spacing.unit * 2,\r\n },\r\n \"&>div:last-child\": {\r\n paddingLeft: spacing.unit,\r\n },\r\n },\r\n bgr: {\r\n color: palette.primary.dark,\r\n marginLeft: spacing.unit * 2,\r\n marginRight: spacing.unit,\r\n },\r\n});\r\n\r\nlet ThemeSelectorItem = (props) => {\r\n const { name, data, classes } = props;\r\n const { primary: colour1, background: colour2 } = data;\r\n const _name = name.toLowerCase();\r\n const colour = \"white\";\r\n const border = \"1px #d9d9d9 solid\";\r\n const border1 = _name.lastIndexOf(colour, 0) === 0 ? border : null;\r\n const border2 =\r\n _name.indexOf(colour, name.length - colour.length) !== -1\r\n ? border\r\n : null;\r\n\r\n return (\r\n \r\n
\r\n
\r\n
{name}
\r\n
\r\n );\r\n};\r\n\r\nThemeSelectorItem = withStyles(styles)(ThemeSelectorItem);\r\n\r\nexport { ThemeSelectorItem };\r\n","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport {Map as ImmutableMap} from 'immutable'\r\nimport ImmutablePropTypes from 'immutable-prop-types'\r\n\r\n// material-ui\r\nimport Select from '@material-ui/core/Select'\r\nimport MenuItem from '@material-ui/core/MenuItem'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// redux (selectors)\r\nimport {getTheme} from 'redux/reducers/theme/selectors'\r\nimport {getThemeData, getSettingsData} from 'redux/reducers/selectors'\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getThemesData} from 'redux/reducers/settings/client/selectors'\r\n\r\nimport {ThemeSelectorItem} from './theme-selector-item'\r\n\r\n// redux (action-types)\r\nimport {SET_THEME} from 'redux/reducers/theme/action-types'\r\n\r\n\r\n// ThemeSelector (not connected)\r\n// --------------------------------------------------------------------\r\n\r\nlet ThemeSelector = (props) =>\r\n{\r\n\tconst {classes} = props;\r\n\r\n\tif (props.themeNames.size === 0) {\r\n\t\treturn null;\r\n\t}\r\n\r\n\tconst selectProps = {\r\n\t\tclassName: classes.bgr,\r\n\t\tinputProps: {className: classes.text},\r\n\t\tonChange: props.setCurrentTheme,\r\n\t\tvalue: props.currentTheme,\r\n\t\tclasses: {icon: classes.text},\r\n\t\trenderValue: (value)=>value\r\n\t}\r\n\r\n\t// const children = props.themeNames.map(themeName => (\r\n\t// \t\r\n\t// \t\t{themeName}\r\n\t// \t \r\n\t// ))\r\n\r\n\tconst children = props.themes.map(theme => {\r\n\t\tconst {name} = theme;\r\n\t\treturn (\r\n\t\t\r\n\t\t\t \r\n\t\t \r\n\t)});\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{children}\r\n\t\t \r\n\t);\r\n}\r\n\r\nThemeSelector.propTypes = {\r\n\tclasses: PropTypes.object.isRequired,\r\n\tcurrentTheme: PropTypes.string.isRequired,\r\n\tsetCurrentTheme: PropTypes.func.isRequired,\r\n\tthemeNames: ImmutablePropTypes.seq.isRequired,\r\n}\r\n\r\n\r\n// ThemeSelector (connected to store)\r\n// --------------------------------------------------------------------\r\n\r\nconst withDefaultFirst = (a, b) => {\r\n\tif (a === 'Default') { return -1; }\r\n\telse if (b === 'Default') { return 1; }\r\n\treturn 0;\r\n}\r\n\r\nconst mapStoreToProps = (store) => {\r\n\tconst clientSettingsData = getClientSettingsData(getSettingsData(store));\r\n\tconst themesData = getThemesData(clientSettingsData) || ImmutableMap();\r\n\treturn {\r\n\t\tcurrentTheme: getTheme(getThemeData(store)),\r\n\t\tthemeNames: themesData.keySeq().sort(withDefaultFirst),\r\n\t\tthemes: themesData.keySeq().sort(withDefaultFirst).map(theme=>{\r\n\t\t\treturn {\r\n\t\t\t\tname: theme,\r\n\t\t\t\tdata: themesData.get(theme).toJS()\r\n\t\t\t}\r\n\t\t}).toJS()\r\n\t}\r\n};\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tsetCurrentTheme: ({target:{value}}) => {\r\n\t\tdispatch({type: SET_THEME, value})\r\n\t}\r\n});\r\n\r\nThemeSelector = connect(mapStoreToProps, mapDispatchToProps)(ThemeSelector);\r\n\r\n\r\n// ThemeSelector (connected to styles)\r\n// --------------------------------------------------------------------\r\n\r\nconst styles = ({spacing, palette}) => ({\r\n\ttext: {\r\n\t\tcolor: palette.primary.contrastText\r\n\t},\r\n\tbgr: {\r\n\t\tcolor: palette.primary.dark,\r\n\t\tmarginLeft: spacing.unit * 2,\r\n\t\tmarginRight: spacing.unit\r\n\t}\r\n})\r\n\r\nThemeSelector = withStyles(styles)(ThemeSelector);\r\n\r\n\r\n// ThemeSelector (EXPORT)\r\n// --------------------------------------------------------------------\r\n\r\nexport {ThemeSelector}","import React from \"react\";\r\nimport { connect } from \"react-redux\";\r\n\r\n// redux (selectors)\r\nimport { getSettingsData } from \"redux/reducers/selectors\";\r\nimport { getAppSettingsData } from \"redux/reducers/settings/selectors\";\r\nimport { getAppVersion } from \"redux/reducers/settings/app/selectors\";\r\n\r\nimport Chip from \"@material-ui/core/Chip\";\r\nimport Tooltip from \"@material-ui/core/Tooltip\";\r\nimport withStyles from \"@material-ui/core/styles/withStyles\";\r\n\r\nlet VersionNumber = ({ versionNumber, classes }) => {\r\n const chipProps = {\r\n label: `v ${versionNumber}`,\r\n className: classes.chip,\r\n };\r\n const title = `XAMS Player Version ${versionNumber}`;\r\n\r\n return (\r\n \r\n \r\n \r\n );\r\n};\r\n\r\nconst styles = ({ palette }) => {\r\n return {\r\n chip: {\r\n backgroundColor: palette.primary.main,\r\n color: palette.background.light,\r\n },\r\n };\r\n};\r\n\r\nVersionNumber = withStyles(styles)(VersionNumber);\r\n\r\nconst mapStoreToProps = (store) => {\r\n const settingsData = getSettingsData(store);\r\n const appSettingsData = getAppSettingsData(settingsData);\r\n\r\n const versionNumber = getAppVersion(appSettingsData);\r\n\r\n return { versionNumber };\r\n};\r\n\r\nVersionNumber = connect(mapStoreToProps)(VersionNumber);\r\n\r\nexport { VersionNumber };\r\n","const appBarHeight = 48;\r\n\r\nexport {appBarHeight}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport Toolbar from '@material-ui/core/Toolbar'\r\nimport MaterialAppBar from '@material-ui/core/AppBar'\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\nimport Hidden from '@material-ui/core/Hidden'\r\n\r\n// react\r\nimport {AppBarLogo} from './logo'\r\nimport {BugReporter} from './bug_reporter'\r\nimport { ActivityReporter } from \"components/pages/activity_logs/activity-reporter\"\r\nimport { ProctoringIndicator} from \"components/pages/proctorio/proctoring-indicator\"\r\nimport {LogoutButton} from './logout-button'\r\nimport {ThemeSelector} from './theme-selector'\r\nimport {VersionNumber} from \"./version-number\"\r\nimport {withMessages} from 'components/hocs/messages'\r\nimport {LoadingText} from 'components/presentation/loading-text'\r\nimport {MaterialText} from 'components/presentation/material-text'\r\n\r\n// constants\r\nimport {appBarHeight} from './constants'\r\nimport {MESSAGE_IDS} from 'constants/message-ids'\r\n\r\n\r\nconst welcomeMessageId = MESSAGE_IDS.APP_BAR.WELCOME;\r\n\r\n\r\n// AppBar (not connected to styles/messages)\r\n// ------------------------------------------\r\n\r\nclass AppBar extends React.Component\r\n{\r\n\trender()\r\n\t{\r\n\t\tconst props = {\r\n\t\t\tclasses: {\r\n\t\t\t\tcolorPrimary: this.props.classes.appBar\r\n\t\t\t},\r\n\t\t\tstyle: {height: appBarHeight},\r\n\t\t\tposition: 'relative',\r\n\t\t\televation: 0\r\n\t\t}\r\n\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\t{this.Logo}\r\n\t\t\t\t\t{this.Title}\r\n\t\t\t\t\t
\r\n\t\t\t\t\t{this.WelcomeMessage}\r\n\t\t\t\t\t{this.OtherContent}\r\n\t\t\t\t\t \r\n\t\t\t\t\t \r\n\t\t\t\t\t \r\n\t\t\t\t\t
\r\n\t\t\t\t\t{this.LogoutButton}\r\n\t\t\t\t\t \r\n\t\t\t\t\t \t\t\t\t\t\r\n\t\t\t\t \r\n\t\t\t \r\n\t\t)\r\n\t}\r\n\r\n\tget Logo()\r\n\t{\r\n\t\tif (!this.props.logo) { return null; }\r\n\t\treturn ;\r\n\t}\r\n\r\n\tget Title()\r\n\t{\r\n\t\tif (!this.props.title) { return null; }\r\n\t\t\r\n\t\tconst {classes, loadingTitle} = this.props;\r\n\t\tconst className = `${classes.title} ${classes.text}`;\r\n\t\tconst TitleComponent = loadingTitle ? LoadingText : MaterialText;\r\n\r\n\t\treturn (\r\n \r\n \r\n \r\n {this.props.title}\r\n \r\n \r\n \r\n \r\n {this.props.title}\r\n \r\n \r\n \r\n );\r\n\t}\r\n\r\n\tget WelcomeMessage()\r\n\t{\r\n\t\tif (!this.props.welcomeMessage) {\r\n return null;\r\n\t\t}\r\n\t\t\r\n const { classes, messages } = this.props;\r\n\t\t\r\n\t\treturn (\r\n \r\n \r\n {messages[welcomeMessageId]}\r\n \r\n \r\n );\r\n\t}\r\n\r\n\tget LogoutButton()\r\n\t{\r\n\t\tif (!this.props.logout) { return null; }\r\n\t\treturn ;\r\n\t}\r\n\r\n\tget OtherContent()\r\n\t{\r\n\t\tif (!this.props.other) { return null; }\r\n\t\treturn this.props.other;\r\n\t}\r\n}\r\n\r\nAppBar.propTypes = {\r\n\twelcomeMessage: PropTypes.bool.isRequired,\r\n\tlogout: PropTypes.bool.isRequired,\r\n\tlogo: PropTypes.bool.isRequired,\r\n\tloadingTitle: PropTypes.bool,\r\n\ttitle: PropTypes.string,\r\n\tother: PropTypes.node\r\n}\r\n\r\nAppBar.defaultProps = {\r\n\twelcomeMessage: false,\r\n\tlogout: false,\r\n\tlogo: false\r\n}\r\n\r\n\r\n// AppBar (connected to styles/messages)\r\n// ------------------------------------------\r\n\r\nconst styles = ({palette:{primary}, spacing}) =>\r\n{\r\n\treturn {\r\n\t\ttext: {\r\n\t\t\tcolor: primary.contrastText\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\tmarginLeft: spacing.unit * 2,\r\n\t\t\tmarginRight: spacing.unit * 2\r\n\t\t},\r\n\t\tappBar: {\r\n\t\t\tbackgroundColor: primary.light + \"!important\"\r\n\t\t}\r\n\t}\r\n}\r\n\r\nAppBar = withStyles(styles)(AppBar);\r\nAppBar = withMessages(AppBar);\r\n\r\n\r\n// Export\r\n// ------------------------------------------\r\nexport {AppBar}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\n\r\n// material-ui\r\nimport withStyles from '@material-ui/core/styles/withStyles'\r\n\r\n// react\r\nimport {MaterialScrollWrapper} from 'components/layout/scroll_wrapper/material-scroll-wrapper'\r\nimport {LoadingSpinner} from 'components/presentation/loading-spinner'\r\n\r\n// constants\r\nimport {appBarHeight} from './app_bar/constants'\r\n\r\n\r\nlet PageWrapper = ({classes, children, loading}) => (\r\n\t\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t{children}\r\n\t\t\t \r\n\t\t
\r\n\t \r\n)\r\n\r\nPageWrapper.propTypes = {\r\n\tclasses: PropTypes.object.isRequired,\r\n\tchildren: PropTypes.node.isRequired,\r\n\tloading: PropTypes.bool.isRequired\r\n}\r\n\r\n\r\nconst styles = (theme) => ({\r\n\twrapper: {\r\n\t\theight: `calc(100% - ${appBarHeight}px)`,\r\n\t\tbackground: theme.palette.background.dark,\r\n\t\twidth: '100%'\r\n\t}\r\n})\r\n\r\nPageWrapper = withStyles(styles)(PageWrapper)\r\n\r\n\r\nexport {PageWrapper}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// react\r\nimport {AppPageContext} from './context'\r\n\r\n\r\nconst AppPageProvider = (props) => (\r\n\t\r\n\t\t{props.children}\r\n\t \r\n)\r\n\r\n\r\nexport {AppPageProvider}","\r\n// npm\r\nimport React from 'react'\r\nimport check from 'check-types'\r\n\r\n// react (HOCs)\r\nimport {MessagesProvider} from 'components/hocs/messages/provider'\r\n\r\n// react\r\nimport {PageRouter} from './page-router'\r\nimport {AppBar} from './layout/app_bar/app-bar'\r\nimport {PageWrapper} from './layout/page-wrapper'\r\nimport {AppPageProvider} from 'components/contexts/app_page/provider'\r\n\r\n\r\nclass AppPage extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.appBarProps = {\r\n\t\t\tdefaultValue: {},\r\n\t\t\tstack: []\r\n\t\t}\r\n\r\n\t\tthis.loading = {\r\n\t\t\tdefaultValue: false,\r\n\t\t\tstack: []\r\n\t\t}\r\n\r\n\t\tthis.state = {\r\n\t\t\tappBarProps: this.appBarProps.defaultValue,\r\n\t\t\tloading: this.loading.defaultValue\r\n\t\t};\r\n\r\n\t\tthis.initializeBoundMethods();\r\n\t\tthis.initializeContext();\r\n\t}\r\n\r\n\tinitializeBoundMethods()\r\n\t{\r\n\t\tthis.setStateValue = (name, value) => {\r\n\t\t\tthis[name].stack.push(value);\r\n\t\t\tthis.setState({[name]: value});\r\n\t\t}\r\n\r\n\t\tthis.unsetStateValue = (name) => {\r\n\t\t\tconst stack = this[name].stack;\r\n\t\t\tconst defaultValue = this[name].defaultValue;\r\n\t\t\tstack.pop();\r\n\r\n\t\t\tconst nextValue = stack[stack.length - 1] || defaultValue;\r\n\t\t\tthis.setState({[name]: nextValue});\r\n\t\t}\r\n\t}\r\n\r\n\tinitializeContext()\r\n\t{\r\n\t\tthis.contextValue = {\r\n\t\t\tpushAppBarState: (appBarProps) => {\r\n\t\t\t\tif (!check.object(appBarProps)) { throw \"updateAppBar type error\"; }\r\n\t\t\t\tthis.setStateValue('appBarProps', appBarProps);\r\n\t\t\t},\r\n\t\t\tpushLoadingState: (loading) => {\r\n\t\t\t\tthis.setStateValue('loading', loading);\r\n\t\t\t},\r\n\t\t\trevokeAppBarState: () => {\r\n\t\t\t\tthis.unsetStateValue('appBarProps');\r\n\t\t\t},\r\n\t\t\trevokeLoadingState: () => {\r\n\t\t\t\tthis.unsetStateValue('loading');\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn (\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\t \r\n\t\t\t\t\t\r\n\t\t\t\t\t\t \r\n\t\t\t\t\t \r\n\t\t\t\t \r\n\t\t\t \r\n\t\t)\r\n\t}\r\n}\r\n\r\n\r\nexport {AppPage}","\n// npm\nimport React from 'react'\n\n// react\nimport {AppPage} from './pages/app-page'\n\n// css\nimport './app.css'\n\n\nlet App = () =>\n{\n\treturn ;\n}\n\n\nexport {App}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\nimport {Map as ImmutableMap, fromJS} from 'immutable'\r\n\r\n// material-ui\r\nimport createMuiTheme from '@material-ui/core/styles/createMuiTheme'\r\nimport MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider'\r\n\r\n// redux (selectors)\r\nimport {getSettingsData} from 'redux/reducers/selectors'\r\nimport {getClientSettingsData} from 'redux/reducers/settings/selectors'\r\nimport {getThemesData} from 'redux/reducers/settings/client/selectors'\r\nimport {getThemeData} from 'redux/reducers/selectors'\r\nimport {getTheme} from 'redux/reducers/theme/selectors'\r\n\r\n// utils\r\nimport {ThemeConfig} from 'utils/theme-config'\r\n\r\n// constants\r\nimport {COLOR_NAMES, DEFAULT_THEME} from 'constants/theme'\r\n\r\n\r\nconst colorNames = Object.values(COLOR_NAMES);\r\n\r\n\r\n// Helper methods\r\n// -------------------------------------------------------------------\r\n\r\nconst augmentNonStandardColors = (palette) =>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// MUI creates 'light/dark' variations for primary, secondary and error colours only\r\n{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Any additional colour types (e.g. background) must have their light/dark colours calculated\r\n\tconst {background} = palette;\r\n\tpalette.background = palette.augmentColor(background, 300, 500, 700);\r\n}\r\n\r\nconst convertToMuiPalette = (themeData) =>\r\n{\r\n\tconst muiPalette = {};\r\n\r\n\tcolorNames.forEach(colorName => {\r\n\t\tmuiPalette[colorName] = { main: themeData.get(colorName) }\r\n\t});\r\n\r\n\treturn adjustMuiPalette(themeData, muiPalette);\r\n}\r\n\r\nconst adjustMuiPalette = (themeData, muiPalette) => {\r\n if (\r\n themeData.get(\"primary\") === \"#030303\" &&\r\n themeData.get(\"background\") === \"#800080\"\r\n ) {\r\n // Black on purple hack\r\n muiPalette.background.contrastText = \"#000\";\r\n }\r\n return muiPalette;\r\n};\r\n\r\nconst createDefaultTheme = () =>\r\n{\r\n\tconst themeConfig = new ThemeConfig(DEFAULT_THEME);\r\n\tconst defaultTheme = themeConfig.generateTheme();\r\n\treturn ImmutableMap(fromJS(defaultTheme));\r\n}\r\n\r\nconst getMuiPalette = (themesData, themeName) =>\r\n{\r\n\tconst theme = themesData ? themesData.get(themeName) : createDefaultTheme();\r\n\treturn convertToMuiPalette(theme);\r\n}\r\n\r\n\r\n// AppTheme (not connected to store)\r\n// -------------------------------------------------------------------\r\n\r\nlet AppTheme = ({themeName, themesData, children}) =>\r\n{\r\n\tconst muiThemeConfig = {\r\n\t\tpalette: getMuiPalette(themesData, themeName),\r\n\t\ttypography: {useNextVariants: true}\r\n\t};\r\n\t\r\n\tconst theme = createMuiTheme(muiThemeConfig);\r\n\taugmentNonStandardColors(theme.palette);\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{children}\r\n\t\t \r\n\t)\r\n}\r\n\r\nAppTheme.propTypes = {\r\n\tchildren: PropTypes.node,\r\n\tthemeName: PropTypes.string,\r\n\tthemesData: PropTypes.object\r\n}\r\n\r\n\r\n// AppTheme (connected to store)\r\n// -------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => {\r\n\tconst settingsData = getSettingsData(store);\r\n\tconst clientSettingsData = getClientSettingsData(settingsData);\r\n\tconst currentTheme = getTheme(getThemeData(store)) || \"default\";\r\n\t\r\n\treturn {\r\n\t\tthemeName: getTheme(getThemeData(store)),\r\n\t\tthemesData: getThemesData(clientSettingsData)\r\n\t}\r\n}\r\n\r\nAppTheme = connect(mapStoreToProps)(AppTheme);\r\n\r\n\r\n// EXPORT\r\n// -------------------------------------------------------------------\r\nexport {AppTheme}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {withRouter} from 'react-router-dom'\r\n\r\n// utils\r\nimport {routingBlocker} from 'utils/routing-blocker'\r\n\r\n\r\nclass AppRouteBlocker extends React.Component\r\n{\r\n\tcomponentDidMount()\r\n\t{\r\n\t\tthis.unblock = routingBlocker.block(this.props.history);\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn this.props.children;\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tthis.unblock();\r\n\t}\r\n}\r\n\r\nAppRouteBlocker.propTypes = {\r\n\thistory: PropTypes.object.isRequired\r\n}\r\n\r\nAppRouteBlocker = withRouter(AppRouteBlocker);\r\n\r\n\r\nexport {AppRouteBlocker}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {BrowserRouter} from 'react-router-dom'\r\n\r\n// react\r\nimport {AppRouteBlocker} from './app-route-blocker'\r\nimport {getPopup} from 'utils/routing-blocker'\r\n\r\n\r\nconst AppRouter = (props) => (\r\n\t\r\n\t\t\r\n\t\t\t{props.children}\r\n\t\t \r\n\t \r\n)\r\n\r\nAppRouter.propTypes = {\r\n\tchildren: PropTypes.node\r\n}\r\n\r\n\r\nexport {AppRouter}","\r\n// npm\r\nimport React from 'react'\r\nimport {check} from '@xams-utils/check-types'\r\n\r\n// redux (selectors)\r\nimport {getSessionData} from 'redux/reducers/selectors'\r\nimport {getOrgId} from 'redux/reducers/session/client/selectors'\r\nimport {getClientSessionData} from 'redux/reducers/session/selectors'\r\n\r\n// utils\r\nimport {api} from 'libs/api/api'\r\nimport {errorLogger} from 'utils/error-logger'\r\n\r\n\r\nclass ErrorLogger extends React.Component\r\n{\r\n\tcomponentDidCatch(e, info)\r\n\t{\r\n\t\tif (api.isEndpointInitialized()) {\r\n\t\t\tif (check.string(e)) {\r\n\t\t\t\terrorLogger.log({message: e, stackTrace: info.componentStack});\r\n\t\t\t}\r\n\t\t\telse if (e && e.message && e.stack) {\r\n\t\t\t\terrorLogger.log({message: e.message, stackTrace: e.stack});\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst error = e;\r\n\t\t\t\tdebugger\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn this.props.children;\r\n\t}\r\n}\r\n\r\n\r\nexport {ErrorLogger}","\r\n// npm\r\nimport React from 'react'\r\nimport PropTypes from 'prop-types'\r\nimport {connect} from 'react-redux'\r\n\r\n// utils\r\nimport {routingBlocker} from 'utils/routing-blocker'\r\n\r\n// redux (selectors)\r\nimport {getAppData} from 'redux/reducers/selectors'\r\nimport {hasNetworkErrorOccurred} from 'redux/reducers/app/selectors'\r\n\r\n// redux (actions)\r\nimport {setNetworkError} from './actions'\r\n\r\n\r\n// NetworkErrorBrowserNavigation (not connected to store)\r\n// -------------------------------------------------------------------------\r\n\r\nclass NetworkErrorBrowserNavigation extends React.Component\r\n{\r\n\tconstructor(props)\r\n\t{\r\n\t\tsuper(props);\r\n\r\n\t\tthis.unsubscribe = routingBlocker.subscribe((location, action) => {\r\n\t\t\tif (this.props.networkError && action === \"POP\") {\r\n\t\t\t\tthis.props.removeNetworkError();\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n\r\n\trender()\r\n\t{\r\n\t\treturn this.props.children || null;\r\n\t}\r\n\r\n\tcomponentWillUnmount()\r\n\t{\r\n\t\tthis.unsubscribe();\r\n\t}\r\n}\r\n\r\nNetworkErrorBrowserNavigation.propTypes = {\r\n\tnetworkError: PropTypes.bool.isRequired,\r\n\tremoveNetworkError: PropTypes.func.isRequired\r\n}\r\n\r\n\r\n// NetworkErrorBrowserNavigation (connected to store)\r\n// -------------------------------------------------------------------------\r\n\r\nconst mapStoreToProps = (store) => ({\r\n\tnetworkError: hasNetworkErrorOccurred(getAppData(store))\r\n});\r\n\r\nconst mapDispatchToProps = (dispatch) => ({\r\n\tremoveNetworkError: () => dispatch(setNetworkError(false))\r\n});\r\n\r\nconst args = [mapStoreToProps, mapDispatchToProps];\r\nNetworkErrorBrowserNavigation = connect(...args)(NetworkErrorBrowserNavigation);\r\n\r\n\r\n// EXPORT\r\n// -------------------------------------------------------------------------\r\nexport {NetworkErrorBrowserNavigation}","\r\n// npm\r\nimport React from 'react'\r\n\r\n// react\r\nimport {App} from './app'\r\nimport {AppTheme} from './app-theme'\r\nimport {AppRouter} from './app-router'\r\nimport {ErrorLogger} from './error-logger'\r\nimport {NetworkErrorBrowserNavigation} from './network-error-browser-navigation'\r\n\r\n\r\nconst AppWrapper = () =>\r\n{\r\n\treturn (\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\t \r\n\t\t\t\t\t \r\n\t\t\t\t \r\n\t\t\t \r\n\t\t \r\n\t)\r\n}\r\n\r\n\r\nexport {AppWrapper}","// npm\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport { Provider } from \"react-redux\";\n\nimport DateFnsUtils from \"@date-io/date-fns\";\nimport { MuiPickersUtilsProvider } from \"material-ui-pickers\";\n\n// internal\nimport { getStore } from \"redux/store\";\nimport { AppWrapper } from \"components/app-wrapper\";\n\nconst store = getStore();\nconst Xams = () => (\n \n \n \n \n \n);\n\nReactDOM.render( , document.getElementById(\"root\"));\n","function webpackEmptyContext(req) {\n\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\te.code = 'MODULE_NOT_FOUND';\n\tthrow e;\n}\nwebpackEmptyContext.keys = function() { return []; };\nwebpackEmptyContext.resolve = webpackEmptyContext;\nmodule.exports = webpackEmptyContext;\nwebpackEmptyContext.id = 272;"],"sourceRoot":""}