\n {cloneElement(children[0], { onClick: () => toggleDropdown(!open) })}\n {open && children[1]}\n
\n );\n};\n\nexport { DropDown, DropdownHeader, DropdownBody };\n","import React from 'react';\nimport { Link } from 'react-router-dom';\nimport { IconAuthorsNew } from 'shared/components/Icons';\nimport { DropDown, DropdownHeader, DropdownBody } from 'shared/components/DropDown';\nimport { hasAccess } from 'utils/permissionHelper';\nimport { getEventUrl } from 'utils/appHelpers';\n\nconst avatarPlaceholder =\n 'https://i1.wp.com/ggrmlawfirm.com/wp-content/uploads/avatar-placeholder.png?fit=256%2C256&ssl=1';\n\nconst AuthorsDropdown = ({ data = [] }) => {\n const path = hasAccess('event_user') ? getEventUrl() : '';\n return (\n \n
\n
Quiz Flow \n {isQuizInProgress && (\n
\n Leave Quiz\n
\n )}\n
\n
\n
\n {flowSteps.map((step, idx) => {\n return (\n
\n \n {passedQuestions && (\n <>\n {QUIZ_ANSWER_STATUS_OBJ[(step.question?.userAnswerStatus)] === 'Success' && (\n
\n Correct \n
\n )}\n {QUIZ_ANSWER_STATUS_OBJ[(step.question?.userAnswerStatus)] === 'Failure' && (\n
\n Incorrect \n
\n )}\n {QUIZ_ANSWER_STATUS_OBJ[(step.question?.userAnswerStatus)] === 'NoAnswer' && (\n
\n Not Answered \n
\n )}\n {QUIZ_ANSWER_STATUS_OBJ[(step.question?.userAnswerStatus)] === 'Expired' && (\n
\n Expired \n
\n )}\n >\n )}\n
\n {!!question?.timeLimit && getIsCurrentQuestion(idx) && (\n \n \n \n )}\n\n {idx === 0 && (\n <>\n Start Quiz \n {startTime ? (\n <>\n {(!!quiz?.timeLimit || !!lesson.timeLimit) && (\n \n \n \n )}\n >\n ) : (\n \n {quiz?.timeLimit || lesson?.timeLimit\n ? bindDurationSecondsGettingMins(\n (\n (quiz?.timeLimit || lesson?.timeLimit) -\n passedDuration / 60\n ).toFixed(1),\n )\n : 'No time limit'}\n \n )}\n >\n )}\n
\n\n {idx === passThresholdIdx && passThresholdIdx <= flowSteps.length - 2 && (\n
\n Passing Threshold \n
\n )}\n {idx === flowSteps.length - 1 && (\n <>\n {isQuizCompleted &&
}\n
\n Results \n
\n >\n )}\n {getIsCurrentQuestion(idx) && isQuizInProgress && (\n
{currentStep}
\n )}\n
\n {idx !== flowSteps.length - 1 && (\n idx + 1 && 'passed'}`} />\n )}\n \n );\n })}\n
\n \n
\n );\n};\n","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALYAAAAvCAYAAABOmTCPAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABbJSURBVHgB7ZwJdFXV1cdPBsIQIcwJCAp8TAEM4ACConGEWgQnQAQtItJCFVutQ7UL51bFIkoFRSp+KH5IGGUqgxYFmalAmAVkCBDCEBKGBALk+/1f7s26ubzHeyEvlK7evdZZ59xzz9ln2mefPZx7jfHAAw888MADDzzwwAMPPPDAgyAQYS4BaNasWUxkZOQvz5w5c7RFixb/TElJOWM88KAEEGUuAahZs2YLomkRERG/OnDgQPuEhIRtGRkZe4wHHlwgRJp/P0RC0H2JKxB+yM/PvxnOvah58+YTmzRpUtt44MEFwEUTRZKTk6PT0tIqxMXFxZw8eTLqNHDs2LETsbGxVcqUKbMUgs49fvx4G56vpPhLPD8Awf9E3GPDhg2rjQceFANKm7AjW7ZsWQsavgkivePs2bNNiWuSX5ZwFKJN4zmHdBfiP69bt+4lVdImOHTo0GNw7veQvQ+RdS/vVhoPPAgRSo2wW7duXTsvL68PyZ4QcPMgbeUT5kHEI6tVqzZjwYIFp1UeRfIeNsMo0pmEe+Hc640HHoQApULYrVq1ag9RD4Wg28CJi9PGEYq/v3///ndRIo+pf1ddddXvIe6XSS8hfnTjxo37QkGUmJjYiOhKd350dPSp1NTU782FQzR9akhfWtHXq3iuwzgrWCdPGmE1bfxYuXLln60Neg4gjlWpXbv2NaYEQBvbGcf2AK8j0FEeI26EePfyjh07cq+55po4RMA/6SX9PU1YR3IaeBozlvtOnDgxavv27bv0nvE9yvtKnJLvY7G6FYbzC56nrl+//gc13bRp075RUVG5NWrU+FJjpK1HeH8CxjOxYcOGlWJiYh6kzk3g1VqNAff2ChUqvEBebGEHIyL+Tp32JBP1TNn9PH8bHx+/NtC8FWt+TJghKSmpCUQ9jmS94tG0DyozuBeZsJpVq1Z9fvPmzUeBEcjdSbzrBb6exEODIalXr145JvEDkp3c75jA0yxWIouw1RQDWLCy5cuXv436/QntyKpBXwsHSLowiei14+DBgzNo59Pu3buvfeWVV846cYGnK/0bY0oAtNePyC9h025d5uodylS87LLLlpI1hTWpyPPTpOcRH+W9xL9b6OsnxP0gvAzefdC2bdt45vwd+vehcBEPZrxtSVYn/MDalCN+RM3AgA4Sz6LM3eDcyeaZlZOTMx58V5M/gSDGdm+lSpXuQax8kjJbpTdZ/Y8huo+4BXmLCNeD5zEY2nPkTzclhLBaRRAdEhnAFJL1zIVDGQY5AI7wjmRtcRsWRbL3T+Q/x6JdFgxBuXLlridqG+B1tPCbYoA2a9myZb9ngafx2JUgPSHQro1gseoTnqSd+djkX2LBK5iLC31ofz+x+vuUIz8L4nmWTd2N+H4p5sQi2G9JPyyGgEJ/P/2OPnXq1FjGfTVj7sDzM5Tp3Lhx4wYOXLnUHQ33bmxnQNQ9iNqTfwfcfhA47gRvVzZPNjGkcWYm+D4iPfLIkSM2gS+lbO+srCyV1SZ83oQBwkbYTIKOGYkMiSY88Dhcr4sSW7ZskU1bsnY1JvnBYBU5Xu8hqnKeIg/VqVOnvAkB2KzJLMh8km1M8U+4aoQ/seBhWaxQQJsIwnqQeRLX+wRiucFJfDbQp71E6RBdfebrf0knwlkluml+5yMq7YII+4BnE1x6lEQtrFdd7fo8DyfKgAF9TOxjNqTrE+0i3q3nrVu3npRexPydpnwk+QOIx0gM4SRp5uwPFrMc+r2KZF0TBggbYTNBksN6mDABg48C3xs2h2bQmnwR+JPnq4d8X4969wRBn8AiBu0rbWtMIpA6AYocs7jMbIKsNrl+ysQwlsHIob8zFwGQozvTp4Yk7ya8Sohm7p5wl+NUk7gQD7Gt50SU/yAVAnyXvJaEEYiBMeTJv1AVBrPQFBDv4+gHNkPIZENIPGzEBuioDMntPNcHj0RHU6tWrQowhh60IYZwhr49iZzeCA7ddNOmTauc/dE6S9Yn+ZMJA4RFxhb3Y0ADQyh61BTIhd8QJKvdSrjCFDhn/EFTJkVixXwUpUyIYybpgSg3DQIpTizSXeZcpVFWlcrGIT6w2N2ItFny/eGR+MFmHWksbmQDi5dP3aUswrDs7Ozp4jT2O+TwGhCMxAARsdu5pE06I5Bsz/zNRQwImTEgE59w50l0gwgfJrmO/kmGzwOvTKqyTI0gxEJ0H9KPs6R1so4m/oE+nWJuPyf9N/KW5ebm/gvrVCeeRR9vgysXHJeTHoRiKJHBpzOsWbNmPYQrRvMFZQxK4tdw4q9ITwCf1rgVZU8S95URgbp/JL+vNd7BRMLTgbzZlGtA3knmtr8JA4SFsBmMjo/2AV6fIuyl43Pp9KSKFSsuXLJkiY8YpJAx2E7kP8BjMmUSXH2KYJJEgJokEeC3hIGS+4wfxYkFq8rk9HAocsaq9xrhWeMgNspcy7HdZNWqVZvceEQgKDEvg6vIBrGI+jPaHwQxHHPX4+g9QDRE910kj1Nf7UnD3yJlluM/wwSGPPSJI6YEwCaLYj6lDO6jfyuUh1w8Hf3gZlkxeHxUeRINiDbDPVNNAXFpbGPlMyDsxDqSBbFtI7sbZXyKnERNxvQjYQdl3iT4rjzAfafxrhM4c6UPMXe/Ye6k37ShzCwU0ZlswtPUG0gfYuy+0sdDMKE/w/VF0FqjTBjJcvAdNmGAsJj7IKiH6Nw4V/YuJus74jlYNb5ZsWJF+nlQRFia/J2kH6ReskQR34uIiKVwkI4QTTaTrWNyNXnvMgHPupHAyX8hbmGKclnJfNeR/xp4f+3IPyuuAZ43/eBpSvllJCu5Xk3iOO0l2dEEAXB0BYe42XgWcCp6wkH7XUJCQh84YhGrCH2ZSV86Gw/CAuGSsRs5HyCgY+zg30sZYMH24kXMCVI/PzMz8yD1NpM+bO1gG1dtuJBPVCE+YOf5Q0KbMge6rSYz1q5dmwE3mOzK19h7IJNXduOBICUiFSFq+nREhBoKUQsQlWZgQ+4MxxvtJOqLDJHnyb8kbnaGCMW+rBcuO3as80EOAIjsFJzyCQiiBkfRao7nxRDXHMJSB3HI2ZFsiRaygEhTjnHhrmDbi+GWkvVEeOdYNND8q7GRbnGJIXn04XNfB2NjF8H5dWok2C/B0xCT1B0kUxx1tOi3mXNhfKjOIQvOcDQX5/ptDeaiXSgFZYs/fPjw+n379p3w956T7QYpfszH/4ixkDWcjbakSZMmSRz9snS0Ju8UZT7D4pGSnp7egHl6gzABJjCJzR7HKTOE929T9i+ULWPj5jkLU927OKBkMn2VvjxDXix1n6PuccbwBGtcGQtKCjQg+XyA5k2OG9r+iHcfw6Di5dwiHkk7b4BnRfXq1T/BMXOWvn/Auy85vRaD63Y55ehHHfIyGM9QjcOEAOHi2McCvaBTcUTR4rIM/iOUq/l0fiCE/gxhCfnvmQLZV31xE7Um8iSTZBOI7z0DPOcEIK8juC531V0FAaxRGln6BM9fud5L6ZWzoZB7MZlxbtnaGscsU4oAfolLc0IJFJ+MA6uRPzzM6bVEUxlDJcp+KKYAgQ2FqCsy/1/wrjl5HxNk6RiNstkTGVynlu7DD2FtklACxajuZk7lMJsFruU830osuX0++fHk3ymCJtxA3kAItm+3bt1kybqRvFamwNzaVWWsrukElGJfgzxdU74RRqWT+A5ZvzIyMm42BevQmX7XZhztiadQNpd+S6nNkxQgW7sJAcJF2Of14tHx1YT+ELXMQurkLYQk8t5ix3bE9DaIdCD38B4mwEfITKgcI8biQoUg2y15sga4j9cvnVYL4FM/+G+EmFvYD1oIJtQtW8ucucWULqjvFUMJ8igyF4FO236MYQdi0OMokCko630h3C4QhwiuBvUGIB6NJ7wBnsniqBCMTsU8ghT9v8FIqtvIwDHG0lvyyJ8CJ/1cJ4arTTnPXoQz32YuAGh3t+ziiYmJTgeQrGwbOR2ekEmVfktHeitUnGERRRjoSho+SefKBihSlgnKI95lhSKc07KOlAlQ90dsqr4TgYE1strb4CyAtaEJbbsnVXWmOjNYlLVwJNmbr3VkiyvJKZFq4Y6wFVcnsLlCkq3/3UD/a2rj2+ZAywKVw7hrEJ+CiNPssqzZNsaepJNLVXk3AHHhC57ftFzeoYLMsFfIo0hdiXuFIpvz2kEgQBx8ns33V/ojphdr1VN/d0EX6sdUxnW1xCA2qtZ5VTCcYSFs2t5Dw9/TGcmrmrBY8n5lClzPgt5MrGS6GZTbyDvfpMOtyzORrcmXe7ejH9TiDFL6bMFZMrRgkaNMhPWhgntjrGCyzrHEUPZTmfqceTx3R0YfitPgEHJ4LvXOIWJMmhKXdphSAtl7JXaFUpZy2X64po1nHu9fJ76dk0iiXksd/RDPPOY6lrXpzQn3Nfs0DhydLQVfdn4p31vgyuKQY41LbwrSnzOcCIM41WZYF5smMY0Z4DoN/pvpx0FwX2+dMgfc9am3n771571EqOoWzrn0bzAbtDl97wLN3Aru4eA5G0qfwiKKSGmgE6NNASEKRGSyTdtHuuSigXR2FgOQi3U5QVx+k+QoOtzb+N9kqch7MruZNm3ayD3dkTrrOfIKvVOYCeOJ7nNWkgOBtiYgV+e5EZI/nui4K/sK8Po4Ps6mTOr7s2KEpNhdKNC+iPCOUAJzdg+6g18PHUrdZ4wxRZ5ayi2kfIpOOk69LeTrAtlrnHDyB3wnWzLr8BcYjJiOj9lwqkmulQfyhE1EEK3m8wQEaBOVGI3tZc0RYeOs2WOZU9Ml0mAJkmj5EeFtnr+njOT6iTIemALHUZ7w2e3CVBbT5xdIZtPeaUSQEZTRRpkIUf/D0sXmQOTbTAgQttt95cuXn8Mu1dH/wPnK0UEpDFcGQadyOQzwAzkL9IxXTl/UyEv5nKuorClFzH+aNN0Wg1ME+rRMTgAnR5Lb+y6Un0kpKSm6/afJ7+DqTy/yh8tLZ0oHjoN7uSkhWKLHb1u2bDlK3jyIJw15WnjzIdq3GMM0CEiiWzYEvUw3KNu1a1c+KyurJ3K55iWfE+sd6s3n2ac7YYrdhx7Uk9h3rRWCXw/u34BjP+Fpy+EjeXw5Sp+Yj0/8oN2nkJvHUaYOeXslsmIRO8W6jKPOXJhcGvl9jHUSYhkZi2l4Ixtvq3QjwsOc9O2odzlld3ECrLJE2qAQVlsmHb6KTkjzTjIlAIvjjmRi/yBvlmzNLMJKyV1snitsYpdsDsgJ1NaUHNJkv9ZpoItPpGfThyIauK53Qhx/DwWZ5V7vBIEshBvtNA7XveegKX0I67VVbIzrIL7HtZPNhYN25BgI+RURNeko0q8TyyZbyMEFHFG6cVeiC/sOuBz8vtuEuIRXwEk2uAvIrgvH6xQMkS70sOGGyU7MqTMFrvMqMnw9c3EgSnd3nEGZdlrMwBTta4yCs75lUitkerjlq8tP4GonUuWQ123dJtpPuzauQslA7esj7SBtRoV6+zIQlIr3yXJ9SwEpLueWA+Z13dnlyPHdGWAC+pP3IWGl7Vq3C/NuNPmPmfCBzJIddA8E3Lq5pquy7gta+lbzRWT/0dbGKwJJSUm63fY2yW6ObHHrDPB11rebpcmxab+n1b4NmThyOtSqVWud3Rfa2swcv8U4v+V00gcJdRAbZC49w7P0nYG6ogqesrKB83yD1cfpiIQv6E4L5X5J1nDwSMx5BEYgk+0gZ1/koEMm/i3xNziChmKr7g4OiZIJUlhlvmOev+CU1MUxfd0zSHdTIP6OMIZP0COaBnJCBYOwf0EjoHNr2Mk3oXn3p8MaSPXzlaeMHDBzGewQJm6JTTBskPuJ5AHbzQQ+7iLqGOvLjSK4wLGbvFAUDDklWrnyWlgOBykpExEl2oJLVz6dDKAiZd7DStKPPkgB01VNfXAsC1B7CEFWAbcOIcvNSsaw1gSG6xjvBBMigG8EG2GBO5825BArAzH1py+HkU3zyNPJXJe4H3O2nTF1IR6P/HsDeGTDrpucnByB5094Zf5M4JRUnclSDHW/G5FKVxWewoOr71cXU+YhU/DrjNtItyaeTCyvoGzN0kMGszk2QwOv8S4Oor4dPO/Rh/d5p9t8HckfJiZCLGdOPcIwdIPt0IC4dd0qVapEQtjmQqBUCFuARSILkXs6A3haHsD8gru6ugOsm4BleT5CkGF/Hs+TGaDz6I+ibj8mQdq5Bq5fMKxzNdGF+jWdGZZs/js21uRg/ZNowKKvtjyjNkRbpsN5cvtzPD7HQuoI1h0UJ3FHW2a0lla7wZrbQFu9V69eHVDxtMbSzYQO8kAuCPCuAkSte9k5EKjEQp9zif6mSpFs0KDBvzCj3WgKPIGB+qM73ddBjO2xetlubN0794kmRLfw7iXWSCdmH8RQudi3w8mzqJtlfR9pEDt8TZsCo8Iu1lEuek3YGsp2oKzMwrKapMmOywaSJSXo53/BoNQIG5ArtztxTXEKjpuZodSB4K6H2zxPnbsYZ6oGTl03UYsDDnJXZqJ1rXKxCQEsLX06eHo783VpHq6RqLvG1kewfRCB5ErWnYaQbbsCbTTa+U7f8kHUJbqSWkyQlSKTdnMZz1H3S/JFQ9HMs8Yr0aSQDsiXc0p5KiNfQ2E9ycfa8IgJOill+tPpqJN2IJvlBaf+4wYL3xnmM1pmWE47MQff3wisdrPZ/I9C2JPg9H80JYRSI2zZlyVTEsQtzvtPECkOmJMaMrjHmGjJejK/TWEBXoYTnHNfmp3eVHcS/KCaC0c43/XYQpDZjj7KxitZupAb69i0vuTw/erBsoUPo2yqPmy1nDvn/YZRKygFmvKfsymGWPe0LxrQtmzLuoR0AGIpdGhAMDUZR0P61VccmffLKKf3vQ4ePHgL8vlW8m7neSfxZupuliubOgOINeZnKSNTXS/ql4souKoskaU6J5tEuED3aTQfs5nXezHl9QXHTOrfLBGGd+LgPobBJpJ5UbdCvzIlhNLk2LI5N7fuH/wf8qP+5rTBcrnKIaB7D1fwLDn2aiarvuV+/QcTOpRFWOjPwSJgghrDbcY68/ILPhYt1pffcJ4lyNLvM5Hu7yMj9LGB8zcAbAR97LCARdamuks3CXnWpSuJMtLw9UsDfSy7k/A1yuXUbdu2+f2wgE2cZn3qdsHAWFMDvDouEYQ58t2PJxbnlld3mz79iihgnXuJ7uckXIbIt5nnZpTTFzbiovt4N0BeWCmIllNHDhLRyj/141Di2uT3ghBXoYdEShElP5m5mU+5A7wr3Mikd0kHwQY+Oy4uTpxYsv8fiHUp7a/I02PQV/qYgs2Uh119Mu3q8tZd1AnJy+gPSu1OrvXFhb5mkdzYjLgWAznnPgj5soToEswiFmAcO3+x/YXNpQwyc7GwVRij70KSXNxsxuyuXbsecv9u4WICIkEchFLRfkZ5OwuxpLMePmeVfi1HdNjpaNImTk9P13edMRDabufFMUtJrysnTHx8/B6UOSl2FbGyZNgbX34GNnK07p1DlPGkc22xRM+cWjmW4h9Rv379mvSvCu1k/vzzz9r4+arP/MXo3rzq6FIbc1uVZ32lE1SB8QelftlcXq2jR4/Wo+O1eExgkn4NMd9Ex1+EELboxy9M/i44hM/rZTzw4D8REElGEfIrV65cz3jgQSnBpfAbYQ88CDuUmiiCbCajvWybReRqxBA5BRrp1hax+8ub3Yglb4b6XaEHHgSCUrOKIEu3kIfLuDaPZVmTe/kmP9XSY2JiRig2HnhQAihN5TESl228P0tIIICosy+yI8MDDzzwwAMPPPDAAw888MADDzz4b4X/B6FHuP0tlf1NAAAAAElFTkSuQmCC\"","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport Button from 'shared/components/Button';\nimport Logo from 'assets/logos/ACE-logo_final-White.png';\nimport {\n BatteryIcon,\n DurationIcon,\n ModalCloseIcon,\n NumberIcon,\n PassThresholdIcon,\n TerminateLeaveIcon,\n TimeLimitIcon,\n} from 'shared/components/Icons';\n\nexport const QuizStartModal = ({\n isOpen,\n onClose,\n onStart,\n lesson,\n passedDuration = 0,\n currentAttempt,\n}) => {\n if (!isOpen) return null;\n\n return ReactDOM.createPortal(\n
\n
e.stopPropagation()}>\n
\n
\n
\n \n \n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla pharetra aliquet lectus\n malesuada pulvinar.{' '}\n
\n
\n
\n
Before you begin \n
\n Each Answer will be submitted by clicking next. You can't go back to change your\n answers. There are answers for which individual time limit and if you not submit\n before limit ends the answer will be considered as wrong.\n
\n
\n
\n
Overview \n
\n The details about the quiz duration, time limit etc. are shown below.\n
\n
\n
\n
\n
\n \n Number Of Questions \n
\n
{lesson.steps?.length} \n
\n
\n
\n \n Number Of attempts \n
\n
\n {currentAttempt}/{lesson.attempts}\n \n
\n
\n
\n
{lesson.passingThreshold}% \n
\n
\n
\n
\n
\n
\n \n Duration \n
\n
\n {lesson.timeLimit\n ? `~${(lesson.timeLimit - passedDuration / 60).toFixed(0)} min`\n : 'No Limit'}\n \n
\n
\n
\n \n Time Limit \n
\n
{lesson.timeLimit ? `${lesson.timeLimit} min` : 'No Limit'} \n
\n
\n
\n \n Terminate on Leave \n
\n
{lesson.terminateOnLeave ? 'Yes' : 'No'} \n
\n
\n
\n
\n
\n
\n \n Cancel\n \n {\n onStart();\n onClose();\n }}\n >\n Start\n \n
\n
\n
\n
,\n document.getElementById('root'),\n );\n};\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React from 'react';\nimport { CSS } from '@dnd-kit/utilities';\nimport { useSortable } from '@dnd-kit/sortable';\n\nexport const SortableImgItem = props => {\n const {\n name,\n id,\n url,\n imageUrl,\n activeItemId,\n overItemId,\n size = 'medium',\n handleSelectItem = () => {},\n isSelected,\n isActiveDragging,\n } = props;\n const { attributes, listeners, setNodeRef, transform, transition } = useSortable({\n id,\n });\n\n const style = {\n transform: CSS.Transform.toString(transform),\n transition,\n };\n\n return (\n
handleSelectItem(id)}\n ref={setNodeRef}\n style={style}\n {...attributes}\n {...listeners}\n >\n
\n {activeItemId !== id &&
}\n
\n
\n );\n};\n","import React from 'react';\nimport { useDroppable } from '@dnd-kit/core';\nimport { useSortable } from '@dnd-kit/sortable';\nimport { CSS } from '@dnd-kit/utilities';\n\nexport const Column = ({\n children,\n id,\n overColumnId,\n column,\n className,\n handleClickNameEdit,\n handleChangeColumnName,\n handleSubmitName,\n questionIdx,\n columnIndex,\n handleRemoveColumn,\n}) => {\n const { setNodeRef: setFirstDroppableRef } = useDroppable({\n id,\n });\n\n const isColumnOvered =\n overColumnId === id || (column.items && column.items.some(item => item.id === overColumnId));\n\n return (\n
\n
\n {!column.isEditing &&
{column.columnName} }\n \n
\n {children}\n
\n
\n );\n};\n","import React from 'react';\nimport { useDroppable } from '@dnd-kit/core';\n\nexport const DroppableUncategorizedArea = ({ children, handleScroll, id }) => {\n const { setNodeRef: setFirstDroppableRef } = useDroppable({\n id,\n });\n return (\n
\n {children}\n
\n );\n};\n","/**\n * Scrolls the container with the given id to its horizontal end.\n * @param {string} containerId - The id of the container to scroll.\n */\nexport const scrollHorizontallyToContainerEnd = containerId => {\n const containerElm = document.getElementById(containerId);\n if (containerElm) {\n requestAnimationFrame(() => {\n containerElm.scrollLeft = containerElm.scrollWidth;\n });\n }\n};\n\nexport const scrollVerticallyToContainerEnd = containerId => {\n const containerElm = document.getElementById(containerId);\n if (containerElm) {\n requestAnimationFrame(() => {\n containerElm.scrollTop = containerElm.scrollHeight;\n });\n }\n};\n","import React from 'react';\nimport { QUESTION_TYPES_OBJ } from '../constants';\nimport { RadioTypeQuestion } from './RadioQuestion';\nimport { MultiTypeQuestion } from './MultiTypeQuestion';\nimport { BooleanTypeQuestion } from './BooleanTypeQuestion';\nimport { ImageTypeQuestion } from './ImageTypeQuestion';\nimport Button from 'shared/components/Button';\n\nconst Question = {\n [QUESTION_TYPES_OBJ.radio]: RadioTypeQuestion,\n [QUESTION_TYPES_OBJ.multi]: MultiTypeQuestion,\n [QUESTION_TYPES_OBJ.yesno]: BooleanTypeQuestion,\n [QUESTION_TYPES_OBJ['image-category']]: ImageTypeQuestion,\n};\n\nexport const QuestionContainer = ({\n currentStep,\n question,\n questionsCount,\n handleSubmitAnswer,\n isRequired,\n answer,\n setAnswer,\n}) => {\n const isImageType = question?.type === QUESTION_TYPES_OBJ['image-category'];\n\n const QuestionComponent = Question[question.type];\n\n return (\n
\n
\n
\n
{currentStep} \n
{question?.title}
\n
\n {currentStep}/{questionsCount}\n \n
\n
\n {!!question.type && (\n \n )}\n
\n
\n {\n let currentAnswer = [...(answer || [])];\n if (question.type === QUESTION_TYPES_OBJ['image-category']) {\n currentAnswer = (answer || []).map(a => ({\n category: a.name,\n imageIds: a.items.map(img => img.imageId),\n }));\n }\n setAnswer(null);\n handleSubmitAnswer(currentAnswer, question?.type);\n }}\n className={`btn-blue btn-quiz-main`}\n disabled={isRequired && !answer?.length}\n >\n {currentStep === questionsCount ? 'Finish' : 'Next'}\n \n
\n
\n
\n );\n};\n","import React, { useState } from 'react';\nimport { ENGLISH_UPPERCASE_LETTERS } from '../constants';\n\nexport const RadioTypeQuestion = ({ question, setAnswer, currentStep }) => {\n const [selectedAnswerId, setSelectedAnswerId] = useState(null);\n\n const handleRadioChange = (idx, id) => {\n setSelectedAnswerId(id);\n setAnswer([question.answersMulti[idx].answer]);\n };\n\n return (\n <>\n
\n Answer Type: \n Text Single Select \n
\n
\n {question.answersMulti.map((answer, idx) => {\n const isSelected = selectedAnswerId === answer.questionAnswerId;\n return (\n
\n handleRadioChange(idx, answer.questionAnswerId)}\n />\n \n \n {ENGLISH_UPPERCASE_LETTERS[idx]}. {answer.answer}\n \n
\n );\n })}\n
\n >\n );\n};\n","import React, { useState } from 'react';\nimport { ENGLISH_UPPERCASE_LETTERS } from '../constants';\nimport { QuestionMultiCheckIcon } from 'shared/components/Icons';\n\nexport const MultiTypeQuestion = ({ question, setAnswer, currentStep }) => {\n const [selectedAnswers, setSelectedAnswers] = useState([]); // It's now an array to store multiple selections\n\n const handleCheckboxChange = (idx, id) => {\n if (selectedAnswers.includes(id)) {\n setSelectedAnswers(selectedAnswers.filter(item => item !== id));\n setAnswer(prevAnswers => {\n return (prevAnswers || []).filter(a => a !== question.answersMulti[idx].answer);\n });\n } else {\n setSelectedAnswers([...selectedAnswers, id]);\n setAnswer(prevAnswers => [...(prevAnswers || []), question.answersMulti[idx].answer]);\n }\n };\n\n return (\n <>\n
\n Answer Type: \n Text Multiple Select \n
\n
\n {question.answersMulti.map((answer, idx) => {\n const isSelected = selectedAnswers.includes(answer.questionAnswerId);\n return (\n
\n handleCheckboxChange(idx, answer.questionAnswerId)}\n />\n \n \n {isSelected && }\n \n {ENGLISH_UPPERCASE_LETTERS[idx]}. {answer.answer}\n \n
\n );\n })}\n
\n >\n );\n};\n","import React, { useState } from 'react';\nimport { ENGLISH_UPPERCASE_LETTERS } from '../constants';\n\nexport const BooleanTypeQuestion = ({ question, setAnswer, currentStep }) => {\n const [selectedAnswerId, setSelectedAnswerId] = useState(null);\n\n const handleRadioChange = (idx, id) => {\n setSelectedAnswerId(id);\n setAnswer([question.answersMulti[idx].answer]);\n };\n\n return (\n <>\n
\n Answer Type: \n Boolean Type Select \n
\n
\n {question.answersMulti.map((answer, idx) => {\n const isSelected = selectedAnswerId === answer.questionAnswerId;\n return (\n
\n handleRadioChange(idx, answer.questionAnswerId)}\n />\n \n \n {ENGLISH_UPPERCASE_LETTERS[idx]}. {answer.answer}\n \n
\n );\n })}\n
\n >\n );\n};\n","import React, { useEffect, useState } from 'react';\nimport {\n DndContext,\n KeyboardSensor,\n PointerSensor,\n useSensor,\n useSensors,\n DragOverlay,\n} from '@dnd-kit/core';\nimport {\n arrayMove,\n SortableContext,\n sortableKeyboardCoordinates,\n rectSortingStrategy,\n} from '@dnd-kit/sortable';\n\nimport { SortableImgItem } from './components/SortableImageItem';\nimport { Column } from './components/Column';\nimport { DroppableUncategorizedArea } from './components/DroppableUncategorizedArea';\nimport { scrollVerticallyToContainerEnd } from 'utils/domHelpers';\n\nconst getCategories = ansImageCategories => {\n const resCats = [];\n ansImageCategories.forEach((imgCat, idx) => {\n if (!resCats.some(cat => cat.name === imgCat.category)) {\n resCats.push({ name: imgCat.category, id: idx + 1, items: [] });\n }\n });\n return resCats;\n};\n\nexport const ImageTypeQuestion = props => {\n const { handleSelectStep, question, setAnswer, currentStep } = props;\n\n const [imageColumns, setImageColumns] = useState(\n question.imageAnswers.map((col, idx) => ({\n ...col,\n items: [],\n id: idx + 1,\n name: col.columnName,\n })),\n );\n const [unCategorizedImages, setUnCategorizedImages] = useState(\n question.imageAnswers\n .map(iA => [...iA.rightAnswers.map(a => ({ ...a, id: a.questionAnswerId }))])\n .flat(),\n );\n const [hasStepsContainerScroll, setHasStepsContainerScroll] = useState(false);\n const [isDroppedFromColumn, setIsDroppedFromColumn] = useState(false);\n const [isScrolled, setIsScrolled] = useState(false);\n const [isAtEnd, setIsAtEnd] = useState(false);\n const [activeItemId, setActiveItemId] = useState(null);\n const [overItemId, setOverItemId] = useState(null);\n const [selectedItemIdFromColumn, setSelectedItemIdFromColumn] = useState(null);\n const [selectedImg, setSelectedImg] = useState('');\n\n const allColumnItems = imageColumns.map(c => c.items).flat();\n const unCategorizedRowId = 'uncategorized_row';\n let animationFrameId = null;\n\n const sensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: {\n delay: 100,\n tolerance: 5,\n },\n }),\n useSensor(KeyboardSensor, {\n coordinateGetter: sortableKeyboardCoordinates,\n }),\n );\n\n // Helper to get over column index\n const getOverColumnIndex = overId =>\n imageColumns.findIndex(\n column => column.id === overId || column.items.some(item => item.id === overId),\n );\n\n // Helper function to handle drag from a column to unCategorizedImages\n const handleFromColumnToUncategorized = (active, over) => {\n // Find the item in the column\n const columnIndex = imageColumns.findIndex(column =>\n column.items.some(item => item.id === active.id),\n );\n const itemIndex = imageColumns[columnIndex].items.findIndex(item => item.id === active.id);\n const item = imageColumns[columnIndex].items[itemIndex];\n\n // Remove from the column\n imageColumns[columnIndex].items.splice(itemIndex, 1);\n\n // Find the index in unCategorizedImages where the item will be inserted\n const indexToInsertAt = unCategorizedImages.findIndex(item => item.id === over.id);\n\n // Insert into unCategorizedImages\n const updatedUnCategorizedImages = [...unCategorizedImages];\n updatedUnCategorizedImages.splice(indexToInsertAt, 0, item);\n\n // Update state\n // setQuizConfigObj(updatedUnCategorizedImages); // Update this function to set unCategorizedImages\n setAnswer(imageColumns);\n setImageColumns(imageColumns);\n setUnCategorizedImages(updatedUnCategorizedImages);\n };\n\n // Helper function to handle drag from unCategorizedImages to a column\n const handleFromUncategorizedToColumn = (active, over) => {\n // Find the item and remove it from unCategorizedImages\n const item = unCategorizedImages.find(image => image.id === active.id);\n const overColumnIndex = getOverColumnIndex(over.id);\n\n // Add to the new column\n const updatedColumnItems = [...imageColumns[overColumnIndex].items, item];\n const updatedColumns = [...imageColumns];\n updatedColumns[overColumnIndex].items = updatedColumnItems;\n\n // Update state\n const updatedUnCategorizedImages = [...unCategorizedImages];\n const itemIdx = updatedUnCategorizedImages.findIndex(image => image.id === active.id);\n updatedUnCategorizedImages.splice(itemIdx, 1);\n setAnswer(updatedColumns);\n setImageColumns(updatedColumns);\n setUnCategorizedImages(updatedUnCategorizedImages);\n scrollVerticallyToContainerEnd(`column_body_items_block${imageColumns?.[overColumnIndex]?.id}`);\n };\n\n const handleFromColumnToColumn = (active, over) => {\n const fromColumnIndex = imageColumns.findIndex(column =>\n column.items.some(item => item.id === active.id),\n );\n const itemIndex = imageColumns[fromColumnIndex].items.findIndex(item => item.id === active.id);\n const item = imageColumns[fromColumnIndex].items[itemIndex];\n const overColumnIndex = getOverColumnIndex(over.id);\n\n // Remove from the fromColumn\n imageColumns[fromColumnIndex].items.splice(itemIndex, 1);\n\n // Insert into the new column\n const updatedColumnItems = [...imageColumns[overColumnIndex].items, item];\n const updatedColumns = [...imageColumns];\n updatedColumns[overColumnIndex].items = updatedColumnItems;\n\n // Update state\n setAnswer(imageColumns);\n scrollVerticallyToContainerEnd(`column_body_items_block${imageColumns?.[overColumnIndex]?.id}`);\n };\n\n const handleSortInUncategorizedList = (active, over) => {\n const fromIndex = unCategorizedImages.findIndex(item => item.id === active.id);\n const toIndex = unCategorizedImages.findIndex(item => item.id === over.id);\n\n // Reorder the items\n const updatedUnCategorizedImages = arrayMove(unCategorizedImages, fromIndex, toIndex);\n\n // Update state\n setUnCategorizedImages(updatedUnCategorizedImages);\n };\n\n const handleSortInsideColumn = (active, over) => {\n const fromIndex = imageColumns.findIndex(col => col.id === active.id);\n const toIndex = imageColumns.findIndex(col => col.id === over.id);\n const updatedColumns = arrayMove(imageColumns, fromIndex, toIndex);\n\n setAnswer(updatedColumns);\n };\n\n const handleDragEnd = event => {\n const { active, over } = event;\n\n // Reset these regardless of whether or not `over` is null.\n setActiveItemId(null);\n setOverItemId(null);\n\n // If drop is outside any container, return\n if (!over) {\n return;\n }\n\n // Check if dragged from unCategorizedImages\n const isFromUnCategorized = unCategorizedImages.some(item => item.id === active.id);\n // Check is dragged from a column\n const isFromAColumn = allColumnItems.some(item => item.id === active.id);\n // Check if dropped on unCategorizedImages\n const isOverUnCategorized = unCategorizedImages.some(item => item.id === over.id);\n const isOveredEmptyUnCategorizedRow = over.id === unCategorizedRowId;\n // Check if dropped on a column\n const overColumnIndex = imageColumns.findIndex(\n column => column.id === over.id || column.items.some(item => item.id === over.id),\n );\n const isOverColumn = overColumnIndex !== -1;\n // Dropped on the uncategorized list from a column\n if (isFromAColumn && isOverUnCategorized) {\n handleFromColumnToUncategorized(active, over);\n }\n\n if (isFromUnCategorized && isOverColumn) {\n handleFromUncategorizedToColumn(active, over);\n if (selectedItemIdFromColumn === active.id) {\n setSelectedItemIdFromColumn(null);\n }\n }\n if (isFromAColumn && isOverColumn) {\n handleFromColumnToColumn(active, over);\n }\n\n if (isFromUnCategorized && isOverUnCategorized) {\n handleSortInUncategorizedList(active, over);\n }\n if (\n imageColumns.some(col => col.id === active.id) &&\n imageColumns.some(col => col.id === over.id)\n ) {\n handleSortInsideColumn(active, over);\n }\n if (isFromAColumn && isOveredEmptyUnCategorizedRow) {\n handleFromColumnToUncategorized(active, over);\n }\n };\n\n const handleDragStart = event => {\n const { active, over } = event;\n const isFromAColumn = allColumnItems.some(item => item.id === active.id);\n setIsDroppedFromColumn(isFromAColumn);\n setActiveItemId(active.id);\n };\n\n const handleDragOver = ({ over }) => {\n setOverItemId(over?.id ? over.id : null);\n };\n\n const handleDragCancel = () => {\n setActiveItemId(null);\n };\n\n const handleScroll = e => {\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n\n const element = e.target;\n animationFrameId = requestAnimationFrame(() => {\n setIsScrolled(element.scrollLeft > 0);\n\n const totalScrollWidth = element.scrollWidth - element.clientWidth;\n setIsAtEnd(element.scrollLeft >= totalScrollWidth);\n });\n };\n\n const handleSelectItem = id => {\n const allImageAnswerOptions = question.imageAnswers.map(a => [...a.rightAnswers]).flat();\n const currentItem = allImageAnswerOptions.find(item => item.questionAnswerId === id);\n if (currentItem) setSelectedImg(currentItem);\n };\n\n const handleSelectItemFromColumn = id => {\n const allImageAnswerOptions = question.imageAnswers.map(a => [...a.rightAnswers]).flat();\n const currentItem = allImageAnswerOptions.find(item => item.questionAnswerId === id);\n setSelectedItemIdFromColumn(id === selectedItemIdFromColumn ? null : id);\n if (currentItem) setSelectedImg(currentItem);\n };\n\n useEffect(() => {\n const stepsContainer = document.getElementById('img_cat_edit_columns');\n if (stepsContainer) {\n setHasStepsContainerScroll(stepsContainer?.scrollWidth > stepsContainer?.clientWidth);\n }\n }, [unCategorizedImages]);\n\n useEffect(() => {\n return () => {\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n };\n }, []);\n\n return (\n <>\n
\n \n Sort the items by dragging them to the appropriate boxes.{' '}\n \n {selectedImg.description} \n
\n
\n
\n \n col.id)} strategy={rectSortingStrategy}>\n \n
\n {hasStepsContainerScroll && isScrolled &&
}\n {hasStepsContainerScroll && !isAtEnd &&
}\n
\n {imageColumns.map((column, columnIndex) => {\n return (\n
id === activeItemId) && overItemId\n }\n id={column.id}\n key={columnIndex}\n className={`${imageColumns.length < 3 && 'wide'}`}\n >\n item.id)}\n strategy={rectSortingStrategy}\n >\n \n
\n {column.items.map((item, index) => (\n
\n ))}\n
\n \n \n );\n })}\n
\n \n \n \n \n {unCategorizedImages.map((item, index) => {\n return (\n \n );\n })}\n \n \n
\n \n {!imageColumns.some(({ id }) => id === activeItemId) && (\n \n {activeItemId ? (\n item.id === activeItemId,\n )}\n size={allColumnItems.some(item => item.id === activeItemId) ? 'small' : 'medium'}\n isActiveDragging={true} // You can use this prop to style the overlay if you want\n />\n ) : null}\n \n )}\n \n
\n >\n );\n};\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport Button from 'shared/components/Button';\nimport Logo from 'assets/logos/ACE-logo_final-White.png';\nimport {\n BatteryIcon,\n DurationIcon,\n ModalCloseIcon,\n NumberIcon,\n PassThresholdIcon,\n TerminateLeaveIcon,\n TimeLimitIcon,\n} from 'shared/components/Icons';\nimport { bindDuration } from 'utils/appHelpers';\n\nexport const QuizTerminateModal = ({\n isOpen,\n onClose,\n onTerminate,\n lesson,\n currentAttempt,\n passedDuration = 0,\n}) => {\n if (!isOpen) return null;\n\n return ReactDOM.createPortal(\n
\n
e.stopPropagation()}>\n
\n
\n
\n \n \n
\n It is a long established fact that a reader will be distracted by the readable content\n of a page when looking at its layout. The point of using Lorem Ipsum is that it has a\n more-or-less normal distribution of letters\n
\n
\n
\n
Before closing the quiz \n
\n You need to terminate quiz before you leave. This will be considered as failed\n attempt.\n
\n
\n
\n
Overview \n
\n It is a long established fact that a reader will be disastracted by the\n
\n
\n
\n
\n
\n \n Number Of Questions \n
\n
{lesson.steps.length} \n
\n
\n
\n \n Number Of attempts \n
\n
\n {currentAttempt || 1}/{lesson.attempts}\n \n
\n
\n
\n
{lesson.passingThreshold}% \n
\n
\n
\n
\n
\n
\n \n Duration \n
\n
\n {lesson.timeLimit ? `~${(passedDuration / 60).toFixed(1)} min` : 'No limit'}\n \n
\n
\n
\n \n Time Limitation \n
\n
\n {lesson.timeLimit\n ? `${bindDuration(passedDuration)}/${lesson.timeLimit} min`\n : 'No Limit'}\n \n
\n
\n
\n \n Terminate on Leave \n
\n
{lesson.terminateOnLeave ? 'Yes' : 'No'} \n
\n
\n
\n
\n
\n
\n \n Cancel\n \n {\n onTerminate();\n onClose();\n }}\n >\n Terminate\n \n
\n
\n
\n
,\n document.getElementById('root'),\n );\n};\n","import React from 'react';\nimport Button from 'shared/components/Button';\nimport { ANSWER_STATUSES } from '../constants';\nimport { bindDurationShowFull } from 'utils/appHelpers';\n\nexport const QuizSuccess = ({\n handleShowAnswers,\n handleClose,\n quizCompeteDetails,\n gotQuizCompletionOnMount,\n}) => {\n const answeredSteps = quizCompeteDetails?.userQuiz?.quiz?.steps.filter(s => s.question);\n const correctAnswersCount =\n answeredSteps &&\n answeredSteps.filter(s => s.question.userAnswerStatus === ANSWER_STATUSES.Success).length;\n const inCorrectAnswersCount =\n answeredSteps &&\n answeredSteps.filter(s => s.question.userAnswerStatus === ANSWER_STATUSES.Failure).length;\n const totalAnswersCount = quizCompeteDetails?.userQuiz?.quiz?.steps?.length;\n const passedPercent = ((correctAnswersCount / totalAnswersCount) * 100).toFixed(0);\n return (\n
\n
\n
\n
{`Your Result: ${correctAnswersCount}/${totalAnswersCount} points (${passedPercent}%)`} \n \n
\n
\n
\n Status: Passed \n
\n
\n Time:{' '}\n \n {quizCompeteDetails.userQuiz.duration\n ? `${bindDurationShowFull(quizCompeteDetails.userQuiz.duration)} min`\n : 'No Limit'}\n \n
\n
\n Correct answers: {correctAnswersCount} \n
\n
\n Incorrect answers: {inCorrectAnswersCount} \n
\n
\n
\n
\n {gotQuizCompletionOnMount ? 'Quiz Passed' : 'Congratulations!'}\n
\n
you’ve successfully passed the quiz
\n
\n
\n
\n \n Close the quiz\n \n \n Show Answers\n \n
\n
\n
\n );\n};\n","import React from 'react';\nimport Button from 'shared/components/Button';\nimport { ANSWER_STATUSES } from '../constants';\nimport { bindDurationShowFull } from 'utils/appHelpers';\n\nexport const QuizFailure = ({\n handleRetakeQuiz,\n handleRetakeCourse,\n quizCompeteDetails,\n totalAttemptsCount,\n showWhatIsWrong,\n handleClickOk,\n handleShowAnswers,\n currentAttempt,\n}) => {\n const answeredSteps = quizCompeteDetails?.userQuiz?.quiz?.steps.filter(s => s.question);\n const correctAnswersCount =\n answeredSteps &&\n answeredSteps.filter(s => s.question.userAnswerStatus === ANSWER_STATUSES.Success).length;\n const inCorrectAnswersCount =\n answeredSteps &&\n answeredSteps.filter(s => s.question.userAnswerStatus === ANSWER_STATUSES.Failure).length;\n const totalAnswersCount = quizCompeteDetails?.userQuiz?.quiz?.steps?.length;\n const passedPercent = ((correctAnswersCount / totalAnswersCount) * 100).toFixed(0);\n return (\n
\n
\n
\n
{`Your Result: ${correctAnswersCount}/${totalAnswersCount} points (${passedPercent}%)`} \n \n
\n
\n
\n Status: Not Passed \n
\n
\n Time:{' '}\n \n {quizCompeteDetails.userQuiz.duration\n ? `${bindDurationShowFull(quizCompeteDetails.userQuiz.duration)} min`\n : 'No Limit'}\n \n
\n
\n Correct answers: {correctAnswersCount} \n
\n
\n Incorrect answers: {inCorrectAnswersCount} \n
\n
\n Attempt number:{' '}\n \n {currentAttempt}/{totalAttemptsCount}\n \n
\n
\n
\n
Oops...
\n
you’re almost there... Try it again!
\n
\n
\n {!!showWhatIsWrong && (\n
\n What Did I do wrong?\n \n )}\n
\n {quizCompeteDetails.attempts === totalAttemptsCount ? (\n \n Ok\n \n ) : (\n \n Retake quiz\n \n )}\n \n Retake course\n \n
\n
\n
\n );\n};\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React, { useState } from 'react';\n\nconst QuizViewImageColumn = ({ column, columnsCount, handleSelectImg, selectedImg }) => {\n const { columnName, rightAnswers, userAnswers } = column;\n const incorrectUserAnswers = userAnswers.filter(\n ({ imageId }) => !rightAnswers.some(rA => rA.imageId === imageId),\n );\n return (\n
\n
{columnName}
\n
\n
\n {rightAnswers?.length &&\n rightAnswers.map((imgItem, idx) => {\n const isAnsweredByUser = userAnswers.some(\n ({ imageId }) => imageId === imgItem.imageId,\n );\n return (\n
handleSelectImg(imgItem)}\n key={idx}\n className={`img_item ${selectedImg?.imageId === imgItem.imageId && 'selected'}`}\n >\n {isAnsweredByUser &&
}\n
\n
\n );\n })}\n {!!incorrectUserAnswers?.length &&\n incorrectUserAnswers.map((imgItem, idx) => {\n const isAnsweredByUser = userAnswers.some(\n ({ imageId }) => imageId === imgItem.imageId,\n );\n return (\n
handleSelectImg(imgItem)}\n key={idx}\n className={`img_item ${selectedImg?.imageId === imgItem.imageId && 'selected'}`}\n >\n {isAnsweredByUser &&
}\n
\n
\n );\n })}\n
\n
\n
\n );\n};\n\nexport const ImageTypeView = ({ question }) => {\n const [selectedImg, setSelectedImg] = useState(null);\n const handleSelectImg = imgItem => {\n setSelectedImg(imgItem.imageId === selectedImg?.imageId ? null : imgItem);\n };\n return (\n <>\n
\n \n Sort the items by dragging them to the appropriate boxes.{' '}\n \n {selectedImg && selectedImg.description} \n
\n
\n {question.imageAnswers &&\n question.imageAnswers.map((column, colIdx) => {\n return (\n \n );\n })}\n
\n >\n );\n};\n","import React, { useState } from 'react';\nimport { RadioTypeView } from './components/RadioTypeView';\nimport { MultiTypeView } from './components/MultiTypeView';\nimport { BooleanTypeView } from './components/BooleanTypeView';\nimport { ImageTypeView } from './components/ImageTypeView';\nimport Button from 'shared/components/Button';\nimport { ANSWER_STATUSES } from '../../constants';\n\nconst QuestionViewComponent = {\n radio: RadioTypeView,\n multi: MultiTypeView,\n yesno: BooleanTypeView,\n ['image-category']: ImageTypeView,\n};\n\nconst QuizResults = ({ results = [], totalLength }) => {\n const [currentStep, setCurrentStep] = useState(0);\n\n const nextStep = () => {\n if (currentStep < results.length - 1) {\n setCurrentStep(prev => prev + 1);\n }\n };\n\n const prevStep = () => {\n if (currentStep > 0) {\n setCurrentStep(prev => prev - 1);\n }\n };\n\n const currentResult = results[currentStep];\n const isSucceed = currentResult.status === ANSWER_STATUSES.Success;\n const isFailed = currentResult.status === ANSWER_STATUSES.Failure;\n const isExpired = currentResult.status === ANSWER_STATUSES.Expired;\n const isNotAnswered = currentResult.status === ANSWER_STATUSES.NoAnswer;\n const QuestionComponent = QuestionViewComponent[currentResult.type];\n\n return (\n
\n
\n
\n
{currentStep + 1} \n
{currentResult?.title}
\n
\n \n {currentStep + 1}\n \n /{totalLength}\n \n
\n
\n {isExpired &&
TIME
}\n
\n
\n
\n \n Back\n \n \n Next\n \n
\n
\n
\n );\n};\n\nexport default QuizResults;\n","import React from 'react';\nimport { ENGLISH_UPPERCASE_LETTERS } from '../../../constants';\nimport { QuestionMultiCheckIcon } from 'shared/components/Icons';\n\nexport const RadioTypeView = ({ question }) => {\n return (\n
\n
\n Answer Type: \n Text Single Select \n
\n
\n {question.answersMulti.map((answer, idx) => {\n const isSelectedByUser = answer.isUserAnswer;\n const isRightAnswer = answer.isRightAnswer;\n return (\n
\n \n {!!(isSelectedByUser || isRightAnswer) && (\n \n \n \n )}\n {ENGLISH_UPPERCASE_LETTERS[idx]}. {answer.answer}\n \n
\n );\n })}\n
\n
\n );\n};\n","import React from 'react';\nimport { QuestionMultiCheckIcon } from 'shared/components/Icons';\nimport { ENGLISH_UPPERCASE_LETTERS } from '../../../constants';\n\nexport const MultiTypeView = ({ question }) => {\n return (\n
\n
\n Answer Type: \n Text Multiple Select \n
\n
\n {question.answersMulti.map((answer, idx) => {\n const isSelectedByUser = answer.isUserAnswer;\n const isRightAnswer = answer.isRightAnswer;\n return (\n
\n \n {!!(isSelectedByUser || isRightAnswer) && (\n \n \n \n )}\n {ENGLISH_UPPERCASE_LETTERS[idx]}. {answer.answer}\n \n
\n );\n })}\n
\n
\n );\n};\n","import React from 'react';\nimport { QuestionMultiCheckIcon } from 'shared/components/Icons';\nimport { ENGLISH_UPPERCASE_LETTERS } from '../../../constants';\n\nexport const BooleanTypeView = ({ question }) => {\n return (\n
\n
\n Answer Type: \n Boolean Type Select \n
\n
\n {question.answersMulti.map((answer, idx) => {\n const isSelectedByUser = answer.isUserAnswer;\n const isRightAnswer = answer.isRightAnswer;\n return (\n
\n \n {!!(isSelectedByUser || isRightAnswer) && (\n \n \n \n )}\n {ENGLISH_UPPERCASE_LETTERS[idx]}. {answer.answer}\n \n
\n );\n })}\n
\n
\n );\n};\n","import React from 'react';\nimport ReactDOM from 'react-dom';\n\nconst OfflineModal = ({ isOpen, countdown }) => {\n if (!isOpen) return null;\n\n return ReactDOM.createPortal(\n
\n
\n
You are offline! \n
\n You'll be redirected as time will end unless you come back online.\n
\n
{countdown}
\n
\n
,\n document.getElementById('modal-root'),\n );\n};\n\nexport default OfflineModal;\n","import React, { useEffect, useRef, useState } from 'react';\nimport { connect } from 'react-redux';\n\nimport { getCourses } from 'app/Main/routes/Courses/actions';\nimport { QuizStart } from './components/QuizStart';\nimport { QuizFlow } from './components/QuizFlow';\nimport { QuizStartModal } from './components/QuizStartModal';\nimport { QuestionContainer } from './components/QuestionContainer';\nimport { QuizTerminateModal } from './components/QuizTerminateModal';\nimport { QuizSuccess } from './components/QuizSuccess';\nimport { QuizFailure } from './components/QuizFailure';\nimport { QUESTION_TYPES_OBJ, QUIZ_STATUSES } from './constants';\nimport QuizResults from './components/QuizResults';\nimport OfflineModal from './components/OfflineModal';\nimport { Api } from 'utils/connectors';\nimport { useSnackbar } from 'notistack';\nimport { getError, getEventUrl } from 'utils/appHelpers';\nimport { hasAccess } from 'utils/permissionHelper';\nimport { LESSON_STATUSES } from 'configs/constants';\nimport Loading from 'shared/components/Loading';\n\nconst TypeQuiz = ({ history, lesson, course, currentLessons, setLesson, lessonId, getCourses }) => {\n const { enqueueSnackbar } = useSnackbar();\n const [isModalOpen, setIsModalOpen] = useState(false);\n const [isOpenTerminateModal, setIsOpenTerminateModal] = useState(false);\n const [answer, setAnswer] = useState(null);\n const [isOfflineModalOpen, setIsOfflineModalOpen] = useState(false);\n const [offlineCountdown, setOfflineCountdown] = useState(60);\n const [quiz, setQuiz] = useState(null);\n const [quizStartTime, setQuizStartTime] = useState(null);\n const [currentQuizQuestion, setCurrentQuizQuestion] = useState(null);\n const [isQuizInProgress, setIsQuizInProgress] = useState(false);\n const [isQuizCompleted, setIsQuizCompleted] = useState(false);\n const [currentStep, setCurrentStep] = useState(1);\n const [passedQuestions, setPassedQuestions] = useState(null);\n const [currentAttempt, setCurrentAttempt] = useState(1);\n const [lastQuizDetails, setLastQuizDetails] = useState({});\n const [quizCompeteDetails, setQuizCompleteDetails] = useState(null);\n const [passingThresholdStep, setPassingThresholdStep] = useState(null);\n const [isShowResults, setIsShowResults] = useState(false);\n const [quizUsageSeconds, setQuizUsageSeconds] = useState(0);\n const [isFetching, setIsFetching] = useState(false);\n const [gotQuizCompletionOnMount, setIsGotQuizCompletionOnMount] = useState(false);\n\n const isLessonCompleted = lesson?.status === LESSON_STATUSES.completed;\n\n const offlineTimerRef = useRef(null);\n\n const path = hasAccess('event_user') ? getEventUrl() : '';\n\n const answerResults =\n quizCompeteDetails?.userQuiz?.quiz?.steps &&\n quizCompeteDetails.userQuiz.quiz.steps\n .filter(s => s.question)\n .map(step => ({\n ...step.question,\n status: step?.question?.userAnswerStatus,\n }));\n\n const handleOpenStartModal = () => {\n setIsModalOpen(true);\n };\n\n const handleOpenTerminateModal = () => {\n setIsOpenTerminateModal(true);\n };\n\n const handleOffline = () => {\n setIsOfflineModalOpen(true);\n offlineTimerRef.current = setInterval(() => {\n setOfflineCountdown(prev => {\n if (prev === 1) {\n clearInterval(offlineTimerRef.current);\n history.goBack();\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n };\n\n const handleOnline = () => {\n setIsOfflineModalOpen(false);\n clearInterval(offlineTimerRef.current);\n setOfflineCountdown(60);\n handleStartQuiz();\n };\n\n const handleShowResults = () => {\n setIsShowResults(true);\n };\n\n const getQuizDetails = async isOnMount => {\n try {\n setIsFetching(true);\n const { data } = await Api.get(`/courses/${course.id}/user-quizes`);\n setLastQuizDetails(\n data?.data?.userQuiz?.status === QUIZ_STATUSES.InProgress ? data.data?.userQuiz : {},\n );\n setCurrentAttempt(data?.data?.totalAttempts || currentAttempt);\n if (data?.data?.userQuiz?.status === QUIZ_STATUSES.Success) {\n setQuizCompleteDetails(data.data);\n setQuiz(data.data?.userQuiz?.quiz);\n setPassedQuestions(data.data.userQuiz.quiz.steps.filter(step => !!step.question));\n setIsQuizCompleted(true);\n if (isOnMount) {\n setIsGotQuizCompletionOnMount(true);\n }\n if (data?.data?.totalAttempts === data?.data?.userQuiz?.attempts || !isOnMount) {\n await getCourses();\n }\n }\n setIsFetching(false);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const handleStartQuiz = async () => {\n try {\n const { data } = await Api.post(`/courses/${course.id}/quiz/start`);\n const currStep = data.data.userQuiz.currentStep;\n const currentQuestion = {\n ...data.data.userQuiz.quiz.steps.find(({ step }) => step === currStep)?.question,\n timeLimit: data.data.userQuiz.quiz.steps.find(({ step }) => step === currStep)?.timeLimit,\n };\n setQuiz(data.data.userQuiz.quiz);\n setPassedQuestions(data.data.userQuiz.quiz.steps.filter(step => !!step.question));\n setCurrentQuizQuestion(currentQuestion);\n setQuizStartTime(data.data.userQuiz.startedAt);\n setQuizUsageSeconds(data?.data?.userQuiz?.duration);\n setPassingThresholdStep(data.data.userQuiz.passingThresholdStep);\n setCurrentStep(currStep);\n if (data.data.userQuiz.status === QUIZ_STATUSES.Success) {\n setIsQuizInProgress(false);\n setIsQuizCompleted(true);\n setQuizCompleteDetails(data.data);\n setCurrentStep(data.data.userQuiz.quiz.steps.length + 1);\n await getCourses();\n } else {\n setIsQuizCompleted(false);\n setIsQuizInProgress(true);\n setQuizCompleteDetails({});\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const handleSkipStep = async () => {\n try {\n const res = await Api.post(`/courses/${course.id}/quiz/no-answer`, {\n questionId: currentQuizQuestion.id,\n });\n const { data } = res;\n const currStep = data.data.userQuiz.currentStep;\n const currentQuestion = {\n ...data.data.userQuiz.quiz.steps.find(({ step }) => step === currStep)?.question,\n timeLimit: data.data.userQuiz.quiz.steps.find(({ step }) => step === currStep)?.timeLimit,\n };\n setQuiz(data.data.userQuiz.quiz);\n setPassedQuestions(data.data.userQuiz.quiz.steps.filter(step => !!step.question));\n setCurrentQuizQuestion(currentQuestion);\n setCurrentStep(currStep);\n if (res?.status === 200) {\n setQuizCompleteDetails(res.data.data);\n setIsQuizCompleted(true);\n setIsQuizInProgress(false);\n setQuizStartTime(null);\n setCurrentQuizQuestion(null);\n setCurrentStep(1);\n getQuizDetails();\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const quizUsage = async ({ passedSeconds, passedSecondsOnQuestion }) => {\n try {\n const { data } = await Api.post(`/courses/${course.id}/quiz/usage`, {\n usage: passedSeconds,\n answerUsage: { questionId: currentQuizQuestion.id, usage: passedSecondsOnQuestion },\n });\n setQuizUsageSeconds(data?.data?.usage);\n } catch (err) {\n const { data } = err.response.data;\n const currStep = data?.userQuiz?.currentStep;\n const currentQuestion = {\n ...data?.userQuiz?.quiz?.steps?.find(({ step }) => step === currStep)?.question,\n timeLimit: data?.userQuiz?.quiz?.steps.find(({ step }) => step === currStep)?.timeLimit,\n };\n if (data?.userQuiz?.status === QUIZ_STATUSES.Expired) {\n setIsQuizCompleted(true);\n setIsQuizInProgress(false);\n }\n setPassingThresholdStep(data?.userQuiz?.passingThresholdStep);\n setQuiz(data?.userQuiz?.quiz);\n setQuizCompleteDetails(data);\n setPassedQuestions(data?.userQuiz?.quiz?.steps.filter(step => !!step.question));\n setCurrentQuizQuestion(currentQuestion);\n setCurrentStep(currStep);\n getQuizDetails();\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const handleSubmitAnswer = async (answer, questionType) => {\n if (!answer.length) {\n handleSkipStep();\n return;\n }\n try {\n const body = {\n questionId: currentQuizQuestion.id,\n imageAnswer: questionType === QUESTION_TYPES_OBJ['image-category'] ? answer : null,\n answer: questionType !== QUESTION_TYPES_OBJ['image-category'] ? answer : null,\n };\n\n const res = await Api.post(`/courses/${course.id}/quiz/answer`, body);\n const { data } = res;\n const currStep = data.data.userQuiz.currentStep;\n const currentQuestion = {\n ...data.data.userQuiz.quiz.steps.find(({ step }) => step === currStep)?.question,\n timeLimit: data.data.userQuiz.quiz.steps.find(({ step }) => step === currStep)?.timeLimit,\n };\n setQuiz(data.data.userQuiz.quiz);\n setPassedQuestions(data.data.userQuiz.quiz.steps.filter(step => !!step.question));\n setPassingThresholdStep(data.data.userQuiz.passingThresholdStep);\n setCurrentQuizQuestion(currentQuestion);\n setCurrentStep(currStep);\n if (res?.status === 200) {\n setQuizCompleteDetails(res.data.data);\n setIsQuizCompleted(true);\n setIsQuizInProgress(false);\n setQuizStartTime(null);\n setCurrentQuizQuestion(null);\n setCurrentStep(1);\n getQuizDetails();\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const handleTerminateQuiz = async () => {\n try {\n const res = await Api.post(`/courses/${course.id}/quiz/terminate`);\n setQuizStartTime(null);\n setIsQuizInProgress(false);\n setCurrentQuizQuestion(null);\n setIsQuizCompleted(false);\n setPassedQuestions(null);\n setCurrentStep(1);\n enqueueSnackbar(res.data.message, { variant: 'success' });\n getQuizDetails();\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const handleResetQuiz = () => {\n setQuizStartTime(null);\n setIsQuizInProgress(false);\n setCurrentQuizQuestion(null);\n setIsQuizCompleted(false);\n setPassedQuestions(null);\n setCurrentStep(1);\n setQuiz({});\n };\n\n const handleRetakeCourse = () => {\n const firstLesson = currentLessons[0];\n const lessonPath = `${path}/courses/${course.id}/${firstLesson.type}/${firstLesson.id}`;\n history.push(lessonPath);\n };\n\n const handleClose = () => {\n const lastLesson = currentLessons[currentLessons.length - 1];\n const lessonPath = `${path}/courses/${course.id}/${lastLesson.type}/${lastLesson.id}`;\n history.push(lessonPath);\n };\n\n const handleGoToCourses = () => history.push('/courses');\n\n useEffect(() => {\n if (course) {\n const currLesson = currentLessons.find(lesson => lesson.id === Number(lessonId));\n setLesson(currLesson);\n setCurrentAttempt(currLesson?.attempt);\n if (currLesson) {\n setPassingThresholdStep(\n Math.ceil((currLesson.steps?.length * currLesson.passingThreshold) / 100),\n );\n }\n }\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n getQuizDetails(true);\n return () => {\n setQuizStartTime(null);\n setIsQuizInProgress(false);\n setCurrentQuizQuestion(null);\n setIsQuizCompleted(false);\n setPassedQuestions(null);\n setCurrentStep(1);\n setIsShowResults(false);\n };\n }, []);\n\n useEffect(() => {\n const handleBeforeUnload = e => {\n if (isQuizInProgress) {\n e.preventDefault();\n e.returnValue = 'The quiz will be terminated as you leave! Are you sure you want to leave?';\n return 'The quiz will be terminated as you leave! Are you sure you want to leave?';\n }\n };\n\n if (lesson.terminateOnLeave) {\n window.addEventListener('beforeunload', handleBeforeUnload);\n }\n\n return () => {\n if (lesson.terminateOnLeave) window.removeEventListener('beforeunload', handleBeforeUnload);\n };\n }, [isQuizInProgress]);\n\n useEffect(() => {\n // Only attach listeners if the quiz is in progress and not completed\n if (isQuizInProgress && !isQuizCompleted) {\n window.addEventListener('offline', handleOffline);\n window.addEventListener('online', handleOnline);\n }\n\n return () => {\n window.removeEventListener('offline', handleOffline);\n window.removeEventListener('online', handleOnline);\n clearInterval(offlineTimerRef.current);\n };\n }, [isQuizInProgress, isQuizCompleted]);\n\n useEffect(() => {\n if (isQuizInProgress && lesson.terminateOnLeave) {\n const unblock = history.block((location, action) => {\n // If navigating within the app, show your custom modal\n if (action !== 'PUSH' && action !== 'REPLACE') {\n return; // Allow 'POP' navigation actions (like back button)\n }\n\n handleOpenTerminateModal();\n return false; // Block the navigation\n });\n\n return () => {\n unblock(); // Stop blocking when component unmounts or condition changes\n };\n }\n }, [isQuizInProgress]);\n\n useEffect(() => {\n if (offlineCountdown === 0) {\n handleResetQuiz();\n }\n }, [offlineCountdown]);\n\n return (\n <>\n
setIsModalOpen(false)}\n />\n setIsOpenTerminateModal(false)}\n />\n \n {!isLessonCompleted && !isQuizInProgress && !isQuizCompleted && (\n \n \n
\n )}\n {(isLessonCompleted || isQuizCompleted) && (\n <>\n {isFetching && (\n \n \n
\n )}\n {quizCompeteDetails?.userQuiz?.status === QUIZ_STATUSES.Success &&\n !isShowResults &&\n !isFetching && (\n \n \n
\n )}\n {(quizCompeteDetails?.userQuiz?.status === QUIZ_STATUSES.Failure ||\n quizCompeteDetails?.userQuiz?.status === QUIZ_STATUSES.Expired) &&\n !isShowResults &&\n !isFetching &&\n !gotQuizCompletionOnMount && (\n \n \n
\n )}\n >\n )}\n {isShowResults && (\n \n )}\n {isQuizInProgress && (\n \n \n
\n )}\n \n \n
\n >\n );\n};\n\nexport default connect(\n null,\n { getCourses },\n)(TypeQuiz);\n","import React from 'react';\nimport BackButton from 'shared/BackButton';\nimport CourseHeaderRightNav from '../CourseView/components/CourseHeaderRightNav';\n\nconst CourseHeader = ({ history, course }) => (\n \n
\n \n Evaluation \n
\n
\n
\n);\nexport default CourseHeader;\n","import React, { useEffect, useState } from 'react';\nconst rates = [0, 1, 2, 3, 4];\n\nconst StarRating = ({ onClick, rate = null, isDisabled }) => {\n const [value, setValue] = useState(rate);\n\n const onRate = v => {\n const result = 5 - v;\n setValue(result);\n onClick(result);\n };\n\n useEffect(() => {\n setValue(rate);\n }, [rate]);\n\n return (\n \n
\n {rates.map(v => (\n = 5 - v ? 'active' : ''}`}\n key={v}\n onClick={() => onRate(v)}\n role='presentation'\n >\n ☆\n \n ))}\n
\n
\n );\n};\n\nexport default StarRating;\n","import React, { useEffect, useState } from 'react';\nimport { useSnackbar } from 'notistack';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport FeedbackHeader from './FeedbackHeader';\nimport ButtonLine from 'shared/components/ButtonLine';\nimport { useDispatch } from 'react-redux';\nimport moment from 'moment';\n\nimport FeedCheckbox from './components/FeedCheckbox';\nimport FeedRating from './components/FeedRating';\nimport FeedSelect from './components/FeedSelect';\nimport FeedText from './components/FeedText';\nimport Loading from 'shared/components/Loading';\nimport { getCourses } from '../Courses/actions';\nimport { useHistory } from 'react-router';\nimport { LESSON_STATUSES } from 'configs/constants';\n\nconst feedItemsComp = {\n rating: FeedRating,\n radio: FeedSelect,\n checkbox: FeedCheckbox,\n text: FeedText,\n};\n\nconst CourseFeedback = ({ match, lesson, course }) => {\n const history = useHistory();\n const dispatch = useDispatch();\n const { courseId } = match.params;\n const { enqueueSnackbar } = useSnackbar();\n const [userFeedBack, setUserFeedBack] = useState();\n const [feedItems, setFeedItems] = useState();\n const [fetch, setFetch] = useState(false);\n const [answers, setAnswers] = useState({});\n const courseCompleted =\n lesson &&\n (lesson.status === LESSON_STATUSES.inProgress || lesson.status === LESSON_STATUSES.completed);\n\n const getUserFeedback = async () => {\n try {\n const { data } = await Api.get(`/feedback/getuserfeedbacks/me/${courseId}`);\n const res = {};\n data.data.forEach(item => {\n res[item.courseFeedbackId] = item.value;\n });\n setAnswers(res);\n setUserFeedBack(data.data);\n } catch (err) {\n setUserFeedBack([]);\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const getCourseFeedback = async () => {\n try {\n const { data } = await Api.get(`/feedback/getcoursefeedbacks/${courseId}`);\n const res = data.data.map(item => ({ ...item.feedback, id: item.id }));\n setFeedItems(res);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const updateLesson = async () => {\n try {\n const body = { lessonId: Number(lesson.id), completed: 1, courseId: course.id };\n await Api.post('/courses/updateuserlesson', body);\n dispatch(getCourses());\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const handleSubmit = async e => {\n e.preventDefault();\n try {\n setFetch(true);\n const feedbacks = Object.keys(answers).map(item => ({\n courseFeedbackId: item,\n value: answers[item],\n }));\n const body = { feedbacks };\n const { data } = await Api.post('/feedback/set', body);\n updateLesson();\n getUserFeedback();\n enqueueSnackbar(data.message, { variant: 'success' });\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetch(false);\n }\n };\n\n const handleChange = data => {\n const tempAnswers = { ...answers };\n tempAnswers[data.id] = data.value;\n setAnswers(tempAnswers);\n };\n\n useEffect(() => {\n if (courseCompleted) {\n getUserFeedback();\n getCourseFeedback();\n }\n //eslint-disable-next-line\n }, [courseCompleted]);\n\n if ((!feedItems || !userFeedBack) && courseCompleted) return ;\n\n const isAnswered = userFeedBack && userFeedBack.length;\n\n if (!course.isEvaluationAvailable) {\n return (\n \n
\n
\n No Evaluation is available for this course\n
\n
\n );\n }\n\n return (\n \n
\n {courseCompleted && (\n
\n )}\n {!courseCompleted && (\n
\n Course evaluation will be available after completion of the course.\n
\n )}\n
\n );\n};\n\nexport default CourseFeedback;\n","import React from 'react';\nimport StarRating from 'shared/components/StarRating';\n\nconst FeedRating = ({ data, onChange, answers, isDisabled }) => {\n return (\n \n \n {data.question} \n
\n onChange({ id: data.id, value })}\n rate={answers[data.id]}\n isDisabled={isDisabled}\n />\n \n );\n};\n\nexport default FeedRating;\n","import React from 'react';\nimport Select from 'shared/components/Select';\n\nconst FeedSelect = ({ data, onChange, answers, isDisabled, isAnswered }) => {\n const items = data.feedbackItems.map(item => ({ name: item.label, id: item.label }));\n\n const handleChnage = e => {\n onChange({ id: data.id, value: e.target.value });\n };\n\n return (\n \n \n {data.question} \n
\n \n {!isAnswered ? (\n \n ) : (\n answers[data.id]\n )}\n
\n \n );\n};\n\nexport default FeedSelect;\n","import React from 'react';\n\nconst FeedCheckbox = ({ data, onChange, answers, isDisabled, isAnswered }) => {\n const items = data.feedbackItems.map(item => ({ name: item.label, id: item.feedbackId }));\n\n const handleChange = e => {\n onChange({ id: data.id, value: e.target.checked });\n };\n\n return (\n \n \n {data.question} \n
\n \n {!isAnswered\n ? items.map(item => (\n
\n \n \n {item.name}\n \n
\n ))\n : items.map(item => (\n
\n {answers[data.id] && answers[data.id] !== 'false' ? 'Yes' : 'No'}\n
\n ))}\n
\n \n );\n};\n\nexport default FeedCheckbox;\n","import React from 'react';\n\nconst FeedText = ({ data, onChange, answers, isDisabled, isAnswered }) => {\n const item = data.feedbackItems && data.feedbackItems[0];\n\n const handleChange = e => {\n onChange({ id: data.id, value: e.target.value });\n };\n\n return (\n \n \n {data.question} \n
\n {!isAnswered ? (\n \n ) : (\n answers[data.id]\n )}\n \n );\n};\n\nexport default FeedText;\n","import React, { useState } from 'react';\nimport ButtonLine from 'shared/components/ButtonLine';\nimport ButtonLoading from 'shared/components/ButtonLoading';\nimport { useSnackbar } from 'notistack';\nimport { Api } from 'utils/connectors';\nimport { getError } from 'utils/appHelpers';\nimport { connect } from 'react-redux';\nimport { getCourses } from 'app/Main/routes/Courses/actions';\n\nconst ExpiredView = ({ course, getCourses }) => {\n const [fetch, setFetch] = useState();\n const { enqueueSnackbar } = useSnackbar();\n\n const sentRequest = async () => {\n try {\n setFetch(true);\n await Api.post(`/subscription/extendlicense/${course.subscriptionId}`);\n await getCourses();\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetch(false);\n }\n };\n\n const isSent = course.userSubscriptionStatus === 6;\n\n return (\n \n
Usage period has expired
\n {!isSent && (\n <>\n
You can request to extend access by one additional period.
\n
\n A request will be sent to “{course.name}” subscription administrator\n
\n
\n {fetch ? : 'Send'}\n \n >\n )}\n {isSent && (\n <>\n
Your request to extend usage period has been sent.
\n
\n You will receive a notification as soon as course administrator changes your access\n period.\n
\n >\n )}\n
\n );\n};\n\nexport default connect(\n null,\n { getCourses },\n)(ExpiredView);\n","import React from 'react';\n\nconst TagsViewBlock = ({ tags }) => {\n if (!tags || !tags.length) return null;\n return (\n \n
Tags \n
\n {tags &&\n tags.map(item => {\n const tag = item.tag || item;\n return (\n
\n {tag.name}\n
\n );\n })}\n
\n
\n );\n};\n\nexport default TagsViewBlock;\n","import React from 'react';\nimport { IconWarning } from './Icons';\n\nconst NoAccessCourse = ({ className }) => {\n return (\n <>\n \n \n
\n Access Denied\n \n
\n You don’t have access to this content. Please, contact the sharing person directly\n to solve the issue.\n
\n
\n >\n );\n};\n\nexport default NoAccessCourse;\n","export default __webpack_public_path__ + \"static/media/ace-logo-tsp.a8c8baf3.png\";","import React from 'react';\n\nconst InfoSection = ({ info }) => (\n \n);\nexport default InfoSection;\n","import React from 'react';\nimport { IconPlayFill, IconVideos } from 'shared/components/Icons';\nimport { bindDuration } from 'utils/appHelpers';\nimport { DEFAULT_IMG_URLS } from 'configs/constants';\n\nconst LessonsListSection = ({ lessons, activeLesson, setActiveLesson }) => {\n const lessonClickHandle = (lesson, e) => {\n setActiveLesson(lesson);\n };\n\n return (\n \n {lessons &&\n lessons\n .sort((l1, l2) => l1.lessonOrder - l2.lessonOrder)\n .map((item, i) => {\n const lesson = item.lesson;\n const Icon = IconPlayFill;\n const active =\n (lesson?.id || lesson.title) === (activeLesson?.id || activeLesson?.title);\n const isFeedback = lesson.contentType === 5;\n return (\n
\n
\n
\n \n
\n
\n
\n
\n
\n
{lesson.title}
\n
{lesson.info || lesson.description}
\n
\n
\n {!isFeedback && (\n
\n {lesson.episodes && (\n
\n {lesson.episodes.length} \n \n )}\n {lesson.duration &&
{bindDuration(lesson.duration)}
}\n
\n )}\n
\n );\n })}\n
\n );\n};\n\nexport default LessonsListSection;\n","import React from 'react';\n\nconst EmptyView = ({ text, Icon }) => {\n return (\n \n );\n};\n\nexport default EmptyView;\n","/* eslint-disable consistent-return */\nimport React, { useRef, useState, useEffect } from 'react';\nimport { Player, BigPlayButton } from 'video-react';\nimport { IconPlayFill, IconLock, IconVideo } from 'shared/components/Icons';\nimport { bindDuration, getEpisodeStartTime } from 'utils/appHelpers';\nimport EmptyView from '../EmptyView';\n\nlet checkTime = 0;\n\nconst TypeVideo = ({ lesson }) => {\n const [activePart, setActivePart] = useState(lesson.episodes?.length && lesson.episodes[0]);\n const player = useRef(null);\n\n const videoSettings = {\n src: lesson.contentUrl,\n fluid: false,\n preload: 'auto',\n height: 465,\n };\n\n const epsiodeTimes = lesson.episodes?.map(item => item.startFrom);\n\n const autoSelectEpisode = ({ currentTime, seeking }) => {\n const videoTime = Math.ceil(currentTime);\n if (\n !videoTime ||\n videoTime === checkTime ||\n seeking ||\n !lesson.episodes ||\n !lesson.episodes.length\n )\n return;\n let activeKey = 0;\n checkTime = videoTime;\n epsiodeTimes?.forEach((item, key) => {\n if (checkTime >= item) activeKey = key;\n });\n setActivePart(lesson.episodes[activeKey]);\n };\n\n const handlePartSelect = episode => {\n player.current.seek(episode.startFrom);\n setActivePart(episode);\n };\n\n useEffect(() => {\n const subs = player.current?.subscribeToStateChange(autoSelectEpisode);\n if (lesson.contentUrl) {\n return function cleanup() {\n player.current.pause();\n subs();\n };\n }\n //eslint-disable-next-line\n }, [lesson]);\n\n const episodes = lesson.episodes ? lesson.episodes.filter(i => i.duration) : [];\n\n return (\n <>\n \n {lesson.contentUrl && (\n
\n \n \n )}\n {!lesson.contentUrl && (\n
\n )}\n
\n \n
{lesson.title} - Chapters
\n
\n {episodes &&\n episodes.map((item, index) => {\n const active = true;\n const Icon = active ? IconPlayFill : IconLock;\n const selected = activePart && activePart.title === item.title ? 'active' : '';\n const disabled = !active ? 'disabled' : '';\n const startTime = getEpisodeStartTime(episodes, index, true);\n return (\n
handlePartSelect(item)}\n role='button'\n tabIndex='-1'\n key={item.title + index}\n className={`part-item d-flex align-items-center px-3 ${disabled} ${selected}`}\n >\n
\n \n
\n
\n
{item.title}
\n
{bindDuration(startTime)}
\n
\n
\n );\n })}\n
\n
\n >\n );\n};\n\nexport default TypeVideo;\n","import React, { useState, useRef } from 'react';\nimport EmptyView from '../EmptyView';\nimport { Document, Page, pdfjs } from 'react-pdf';\nimport { IconPdf } from 'shared/components/Icons';\n\nconst pdfVersion = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;\npdfjs.GlobalWorkerOptions.workerSrc = pdfVersion;\n\nconst TypePDF = ({ lesson }) => {\n const [page, setPage] = useState(1);\n const [pages, setPages] = useState([]);\n const pdfContainerRef = useRef();\n const scrollingOffset = useRef();\n const width = window.innerWidth - 230 - 310;\n\n const onDocumentLoadSuccess = document => {\n const { numPages } = document;\n setPages(new Array(numPages).fill(1));\n };\n\n const selectPage = index => {\n setTimeout(() => {\n const elementPDF = document.querySelector('.react-pdf__Document');\n if (elementPDF) elementPDF.scrollTop = 0;\n setPage(index + 1);\n }, 0);\n };\n\n const handlePdfScrolling = () => {\n if (pages.length < 2) return;\n const elementPDF = document.querySelector('.react-pdf__Document');\n if (!elementPDF) return;\n const { scrollHeight, scrollTop, clientHeight } = elementPDF;\n const scrolledTillEnd = scrollTop + clientHeight >= scrollHeight;\n\n setTimeout(() => {\n const nextCondition =\n scrollingOffset.current < scrollTop && scrolledTillEnd && page !== pages.length;\n const prevCondition = scrollingOffset.current > scrollTop && scrollTop === 0 && page !== 1;\n if (nextCondition) {\n elementPDF.scrollTop = 1;\n setPage(page + 1);\n } else if (prevCondition) {\n elementPDF.scrollTop = scrollHeight - clientHeight - 1;\n setPage(page - 1);\n }\n scrollingOffset.current = scrollTop;\n }, 0);\n };\n\n return (\n <>\n \n {lesson.contentUrl && (\n
\n )}\n {!lesson.contentUrl && (\n
\n \n
\n )}\n
\n \n
{lesson.title} - Chapters
\n
\n {pages.map((item, index) => (\n
selectPage(index)}\n role='button'\n tabIndex='-1'\n key={index}\n className={`part-item d-flex align-items-center px-3 ${\n index + 1 === page ? 'active' : ''\n }`}\n >\n
\n
\n ))}\n
\n
\n >\n );\n};\n\nexport default TypePDF;\n","import React from 'react';\nimport placeholder from 'assets/case_placeholder.jpg';\nimport { IconDicom, IconPlayFill } from 'shared/components/Icons';\nimport EmptyView from '../EmptyView';\n\nconst TypeCases = ({ lesson, onCasePlayClick }) => {\n const cases = lesson.episodes;\n return (\n \n {lesson.activeCases?.length === 0 && (\n
\n )}\n {lesson.activeCases?.length !== 0 && (\n <>\n
\n \n \n
\n >\n )}\n
\n );\n};\n\nexport default TypeCases;\n","import React from 'react';\nimport EmptyView from '../EmptyView';\nimport { IconImage } from 'shared/components/Icons';\n\nconst TypeImage = ({ lesson }) => {\n const hasContentImage = lesson.contentUrl || lesson.url;\n return (\n <>\n {hasContentImage && (\n \n
\n
\n )}\n {!hasContentImage && (\n \n \n
\n )}\n >\n );\n};\n\nexport default TypeImage;\n","import React, { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { useParams } from 'react-router-dom';\nimport { useSnackbar } from 'notistack';\nimport moment from 'moment';\nimport ButtonLine from 'shared/components/ButtonLine';\nimport { IconNotification } from 'shared/components/Icons';\nimport FeedCheckbox from './components/FeedCheckbox';\nimport FeedRating from './components/FeedRating';\nimport FeedSelect from './components/FeedSelect';\nimport FeedText from './components/FeedText';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport { getCourses } from 'app/Main/routes/Courses/actions';\n\nconst feedItemsComp = {\n rating: FeedRating,\n radio: FeedSelect,\n checkbox: FeedCheckbox,\n text: FeedText,\n};\n\nconst TypeEvaluation = ({ course }) => {\n const { enqueueSnackbar } = useSnackbar();\n const user = useSelector(state => state.account);\n const [answers, setAnswers] = useState({});\n const [fetch, setFetch] = useState(false);\n const [userFeedBack, setUserFeedBack] = useState([]);\n const [lesson, setLesson] = useState();\n\n const dispatch = useDispatch();\n const params = useParams();\n const courseId = params?.id;\n const lessonId = params?.lessonId;\n const isCourseIdShareToken = isNaN(courseId);\n\n const hasFeedback = !!course.courseFeedbacks?.length;\n const isAnswered = userFeedBack && userFeedBack.length;\n\n const handleChange = data => {\n const tempAnswers = { ...answers };\n tempAnswers[data.feedbackItemId] = data.value;\n setAnswers(tempAnswers);\n };\n\n const getUserFeedback = async () => {\n try {\n const { data } = await Api.get(\n `/feedback/getuserfeedbacks/me/${\n isCourseIdShareToken ? course?.id : courseId || course?.id\n }`,\n );\n const res = {};\n data.data.forEach(item => {\n res[item.courseFeedbackId] = item.value;\n });\n setAnswers(res);\n setUserFeedBack(data.data);\n } catch (err) {\n setUserFeedBack([]);\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const onFormSubmit = async e => {\n e.preventDefault();\n try {\n setFetch(true);\n const feedbacks = Object.keys(answers).map(item => ({\n courseFeedbackId: item,\n value: answers[item],\n }));\n const body = { feedbacks };\n const { data } = await Api.post('/feedback/set', body);\n dispatch(getCourses());\n getUserFeedback();\n enqueueSnackbar(data.message, { variant: 'success' });\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetch(false);\n }\n };\n\n useEffect(() => {\n getUserFeedback();\n }, [user]);\n\n useEffect(() => {\n if (course) setLesson(course?.lessons?.find(lesson => lesson.id === Number(lessonId)));\n //eslint-disable-next-line\n }, [params]);\n\n return (\n \n {hasFeedback && (\n
\n
\n {!!isAnswered && (\n
\n \n Submission Date \n
\n \n {userFeedBack[0] ? moment(userFeedBack[0].createdAt).format('MM/DD/YYYY') : '-'}\n
\n \n )}\n
\n )}\n {!hasFeedback && (\n
\n
\n
\n Feedback form is not configured yet. Please, add content to see the preview.\n
\n
\n )}\n
\n );\n};\n\nexport default TypeEvaluation;\n","import React from 'react';\nimport StarRating from 'shared/components/StarRating';\n\nconst FeedRating = ({ index, data, onChange, answers, isDisabled }) => {\n return (\n \n \n {data.question} \n
\n onChange({ id: index, value, feedbackItemId: data?.id })}\n rate={answers[index] || answers[(data?.id)]}\n isDisabled={isDisabled}\n />\n \n );\n};\n\nexport default FeedRating;\n","import React from 'react';\nimport Select from 'shared/components/Select';\n\nconst FeedSelect = ({ index, data, onChange, answers = [], isDisabled, isAnswered }) => {\n const variants = data?.labels || data?.feedbackItems?.map(item => item.label);\n const items = variants?.map(item => ({ name: item, id: item }));\n\n const handleChnage = e => {\n onChange({ id: index, value: e.target.value, feedbackItemId: data?.id });\n };\n\n return (\n \n \n {data.question} \n
\n \n {!isAnswered ? (\n \n ) : (\n answers[index] || answers[data.id]\n )}\n
\n \n );\n};\n\nexport default FeedSelect;\n","import React from 'react';\n\nconst FeedCheckbox = ({ index, data, onChange, answers, isDisabled, isAnswered }) => {\n const variants = data?.labels || data?.feedbackItems.map(item => item.label);\n const items = variants?.map(item => ({ name: item, id: item.id }));\n\n const handleChange = e => {\n onChange({ id: index, value: e.target.checked, feedbackItemId: data.id });\n };\n\n return (\n \n \n {data.question} \n
\n \n {!isAnswered\n ? items &&\n items.map(item => (\n
\n \n \n {item.name}\n \n
\n ))\n : items &&\n items.map(item => {\n return (\n
\n {answers[index] && answers[index] !== 'false' ? 'Yes' : 'No'}\n
\n );\n })}\n
\n \n );\n};\n\nexport default FeedCheckbox;\n","import React from 'react';\n\nconst FeedText = ({ index, data, onChange, answers, isDisabled, isAnswered }) => {\n const item = data?.labels?.[0] || data?.feedbackItems?.[0]?.label;\n\n const handleChange = e => {\n onChange({ id: index, value: e.target.value, feedbackItemId: data?.id });\n };\n\n return (\n \n \n {data.question} \n
\n {!isAnswered ? (\n \n ) : (\n answers[index] || answers[(data?.id)]\n )}\n \n );\n};\n\nexport default FeedText;\n","import React from 'react';\n\nconst TagsViewBlock = ({ className, tags, title = 'Tags' }) => {\n if (!tags || !tags.length) return null;\n return (\n \n
{title} \n
\n {tags &&\n tags.map((item, i) => {\n const tag = item.tag || item;\n return (\n
\n {tag.label || tag.name}\n
\n );\n })}\n
\n
\n );\n};\n\nexport default TagsViewBlock;\n","import { LESSON_STATUSES } from 'configs/constants';\n\nexport const zoomImageMouseClick = (cornerstone, element, bool, scale, e) => {\n const step = 0.05;\n const viewport = cornerstone.getViewport(element);\n viewport.scale -= bool ? -step : step;\n if (scale) viewport.scale = scale;\n cornerstone.setViewport(element, viewport);\n return false;\n};\n\nexport const getDefaultImageScale = (element, image) => {\n const { clientWidth, clientHeight } = element;\n const { width, height } = image;\n if (width > height) return width > clientWidth ? clientWidth / width : clientHeight / height;\n else return clientHeight / height;\n};\n\nexport const maximazeElement = (cornerstone, element, toggle) => {\n element.classList[toggle ? 'toggle' : 'add']('maximize');\n cornerstone.fitToWindow(element);\n cornerstone.resize(element);\n};\n\nconst getCompletedSteps = (episodes, id) => {\n let body = episodes.filter(item => item.caseId === id);\n body = body.sort((a, b) => a.orderNum - b.orderNum);\n const result = [];\n body.forEach((item, index) => {\n if (item.status === LESSON_STATUSES.inProgress || item.status === LESSON_STATUSES.completed)\n result.push(index);\n });\n return result;\n};\n\nconst setFindings = (data, userLessonId) => {\n try {\n if (data && data.length) {\n data = data.map(f => {\n f.vectorData = JSON.parse(f.vectorData);\n f.vectorData = f.vectorData.map(i => ({ ...i, x: i.pointX, y: i.pointY }));\n return f;\n });\n return data.filter(item => userLessonId === item.userLessonId)[0];\n } else return null;\n } catch (err) {\n return null;\n }\n};\n\nconst checkDicomNameOnViews = (name, newTpe, views) => {\n if (!views[name]) return name;\n const { dicomImageType: currentType } = views[name];\n if (currentType === 'i2d') return false;\n if (currentType === 'g2d' && newTpe === 'r2d') return false;\n return name;\n};\n\nconst getDicomDataName = dicomItem => {\n const { imageLaterality, viewPosition, dicomImageType } = dicomItem;\n const types = { '3dq': '_3DQ', i2d: '_2D', g2d: '_2D', r2d: '_2D', tomo: '_TOMO', sub: '_TOMO' };\n const type = types[dicomImageType];\n return `${imageLaterality}${viewPosition}${type}`;\n};\n\nexport const generateViewerData = (data, lesson) => {\n const result = [];\n const views = {};\n data.caseDicomItems.forEach(({ dicomItem }, index) => {\n const tempName = getDicomDataName(dicomItem);\n const name = checkDicomNameOnViews(tempName, dicomItem.dicomImageType, views);\n if (!name) return;\n views[name] = {\n name,\n position: name[0] === 'R' ? 'right' : 'left',\n protocolName: dicomItem.protocolName,\n imageLaterality: dicomItem.imageLaterality,\n viewPosition: dicomItem.viewPosition,\n dicomImageType: dicomItem.dicomImageType,\n id: dicomItem.id,\n patient: {\n name: dicomItem.patientName,\n age: dicomItem.patientAge,\n sex: dicomItem.patientSex,\n id: dicomItem.patientId,\n birthDate: dicomItem.patientBirthDate,\n },\n images: dicomItem.dicomItemImages.map(img => {\n const emptyFinding = {\n caseId: data.id,\n dicomId: dicomItem.id,\n imageId: img.id,\n userLessonId: lesson.userLessonId,\n };\n return {\n id: img.id,\n url: img.lutUrl || img.url,\n finding: setFindings(img.vectorDatas, lesson.userLessonId),\n emptyFinding,\n geniusAIDataList: img.geniusAIDataList,\n };\n }),\n };\n });\n const completed_steps = getCompletedSteps(lesson.episodes, data.id);\n result.push({ id: data.id, name: data.title, completed_steps, views });\n return result;\n};\n\n(function(target) {\n if (!target || !target.prototype) return;\n target.prototype.arrow = function(startX, startY, endX, endY, controlPoints) {\n var dx = endX - startX;\n var dy = endY - startY;\n var len = Math.sqrt(dx * dx + dy * dy);\n var sin = dy / len;\n var cos = dx / len;\n var a = [];\n a.push(0, 0);\n for (var i = 0; i < controlPoints.length; i += 2) {\n var x1 = controlPoints[i];\n var y1 = controlPoints[i + 1];\n a.push(x1 < 0 ? len + x1 : x1, y1);\n }\n a.push(len, 0);\n for (var j = controlPoints.length; j > 0; j -= 2) {\n var x2 = controlPoints[j - 2];\n var y2 = controlPoints[j - 1];\n a.push(x2 < 0 ? len + x2 : x2, -y2);\n }\n a.push(0, 0);\n for (var k = 0; k < a.length; k += 2) {\n var x3 = a[k] * cos - a[k + 1] * sin + startX;\n var y3 = a[k] * sin + a[k + 1] * cos + startY;\n if (i === 0) this.moveTo(x3, y3);\n else this.lineTo(x3, y3);\n }\n };\n})(CanvasRenderingContext2D);\n\nexport const drawArrow = (c, viewport, e) => {\n c.lineWidth = 1;\n c.strokeStyle = '#00b5ec';\n c.fillStyle = '#00b5ec';\n c.setTransform(1, 0, 0, 1, 10, 0);\n\n c.translate(0, 0);\n c.beginPath();\n c.arrow(50, 50, 0, 0, [0, 2, -10, 2, -10, 5]);\n\n c.fill();\n};\n\nexport const getFindingsCoordinates = points => {\n if (!points || !points.data) return null;\n try {\n const result = points.data.map(i => {\n const item = i.handles.end;\n return { pointX: item.x, pointY: item.y };\n });\n return JSON.stringify(result);\n } catch (err) {\n return null;\n }\n};\n\nexport const createFindingEventData = ({ x, y }) => {\n return {\n visible: true,\n active: true,\n color: undefined,\n invalidated: true,\n handles: {\n end: {\n x,\n y,\n highlight: true,\n active: true,\n },\n },\n };\n};\n\nexport const createGenuieEventData = ({ x, y, shape, percentage }) => {\n return {\n visible: true,\n active: true,\n color: undefined,\n invalidated: true,\n handles: {\n end: {\n x,\n y,\n percentage: percentage,\n shape: shape,\n highlight: true,\n active: true,\n },\n },\n };\n};\n\nexport const getDicomImagesObj = images => {\n const result = {};\n images.forEach(item => {\n result[item.url] = item;\n });\n return result;\n};\n\nexport const sumShapes = (images, shape) => {\n let sum = 0;\n images.forEach(item => {\n if (item.geniusAIDataList && item.geniusAIDataList.length) {\n const shapes = item.geniusAIDataList.filter(i => Number(i.shape) === Number(shape));\n sum += shapes ? shapes.length : 0;\n }\n });\n return sum;\n};\n","export default __webpack_public_path__ + \"static/media/1n.3d8a8f8d.svg\";","export default __webpack_public_path__ + \"static/media/1s.8133eeef.svg\";","export default __webpack_public_path__ + \"static/media/1p.997e7292.svg\";","export default __webpack_public_path__ + \"static/media/1ps.d360f5bb.svg\";","export default __webpack_public_path__ + \"static/media/2n.82ae194a.svg\";","export default __webpack_public_path__ + \"static/media/2s.0553ca14.svg\";","export default __webpack_public_path__ + \"static/media/2p.08f4ee6a.svg\";","export default __webpack_public_path__ + \"static/media/2ps.e14a8de7.svg\";","export default __webpack_public_path__ + \"static/media/3n.ab78d6d2.svg\";","export default __webpack_public_path__ + \"static/media/3s.f2ac7237.svg\";","export default __webpack_public_path__ + \"static/media/3p.9afb207e.svg\";","export default __webpack_public_path__ + \"static/media/3ps.af5584af.svg\";","export default __webpack_public_path__ + \"static/media/4n.e692a18d.svg\";","export default __webpack_public_path__ + \"static/media/4s.e440f2f3.svg\";","export default __webpack_public_path__ + \"static/media/4p.0738b555.svg\";","export default __webpack_public_path__ + \"static/media/4ps.c52e9bb8.svg\";","export default __webpack_public_path__ + \"static/media/5n.f8956da8.svg\";","export default __webpack_public_path__ + \"static/media/5s.91fac24b.svg\";","export default __webpack_public_path__ + \"static/media/5p.8f2d2562.svg\";","export default __webpack_public_path__ + \"static/media/5ps.4cc5571a.svg\";","export default __webpack_public_path__ + \"static/media/6n.8d04a150.svg\";","export default __webpack_public_path__ + \"static/media/6s.442e0709.svg\";","export default __webpack_public_path__ + \"static/media/6p.8ff09964.svg\";","export default __webpack_public_path__ + \"static/media/6ps.24c75292.svg\";","export default __webpack_public_path__ + \"static/media/7n.b1798722.svg\";","export default __webpack_public_path__ + \"static/media/7s.1cf21eb2.svg\";","export default __webpack_public_path__ + \"static/media/7p.14eb7ee8.svg\";","export default __webpack_public_path__ + \"static/media/7ps.9e5f0f2d.svg\";","export default __webpack_public_path__ + \"static/media/8n.de3f7d2c.svg\";","export default __webpack_public_path__ + \"static/media/8s.a8570d99.svg\";","export default __webpack_public_path__ + \"static/media/8p.a01921f6.svg\";","export default __webpack_public_path__ + \"static/media/8ps.5155c21f.svg\";","export default __webpack_public_path__ + \"static/media/9n.b47f982b.svg\";","export default __webpack_public_path__ + \"static/media/9s.0bafdf0a.svg\";","export default __webpack_public_path__ + \"static/media/9p.d9c363a7.svg\";","export default __webpack_public_path__ + \"static/media/9ps.67b49787.svg\";","export default __webpack_public_path__ + \"static/media/11n.c6802432.svg\";","export default __webpack_public_path__ + \"static/media/11s.b00c9cd2.svg\";","export default __webpack_public_path__ + \"static/media/11p.b55d9464.svg\";","export default __webpack_public_path__ + \"static/media/11ps.d6ddb562.svg\";","export default __webpack_public_path__ + \"static/media/12n.7318717c.svg\";","export default __webpack_public_path__ + \"static/media/12s.cc420426.svg\";","export default __webpack_public_path__ + \"static/media/12p.d1b2c74a.svg\";","export default __webpack_public_path__ + \"static/media/12ps.a09149b9.svg\";","export default __webpack_public_path__ + \"static/media/heatmapn.637323d9.svg\";","export default __webpack_public_path__ + \"static/media/heatmaps.8811c071.svg\";","export default __webpack_public_path__ + \"static/media/1p.083d9c5d.svg\";","export default __webpack_public_path__ + \"static/media/1a.ffc6ed4a.svg\";","export default __webpack_public_path__ + \"static/media/2p.c886a429.svg\";","export default __webpack_public_path__ + \"static/media/2a.1f36729f.svg\";","export default __webpack_public_path__ + \"static/media/4p.e4abf33b.svg\";","export default __webpack_public_path__ + \"static/media/4a.343ef825.svg\";","export default __webpack_public_path__ + \"static/media/5p.fba6bffc.svg\";","export default __webpack_public_path__ + \"static/media/5a.fc4fdc8a.svg\";","export default __webpack_public_path__ + \"static/media/6p.8b034a1f.svg\";","export default __webpack_public_path__ + \"static/media/6a.5fd8d6f1.svg\";","export default __webpack_public_path__ + \"static/media/7p.8c67ad2e.svg\";","export default __webpack_public_path__ + \"static/media/7a.cff7a8a9.svg\";","export default __webpack_public_path__ + \"static/media/8p.c2c2fa61.svg\";","export default __webpack_public_path__ + \"static/media/8a.01e0a731.svg\";","export default __webpack_public_path__ + \"static/media/9p.5fbd2803.svg\";","export default __webpack_public_path__ + \"static/media/9a.a5b7bde8.svg\";","export default __webpack_public_path__ + \"static/media/10p.4bfc3d5d.svg\";","import step1Icon from 'assets/stepsnew/1n.svg';\nimport step1IconActive from 'assets/stepsnew/1s.svg';\nimport step1IconPassed from 'assets/stepsnew/1p.svg';\nimport step1IconPActive from 'assets/stepsnew/1ps.svg';\n\nimport step2Icon from 'assets/stepsnew/2n.svg';\nimport step2IconActive from 'assets/stepsnew/2s.svg';\nimport step2IconPassed from 'assets/stepsnew/2p.svg';\nimport step2IconPActive from 'assets/stepsnew/2ps.svg';\n\nimport step3Icon from 'assets/stepsnew/3n.svg';\nimport step3IconActive from 'assets/stepsnew/3s.svg';\nimport step3IconPassed from 'assets/stepsnew/3p.svg';\nimport step3IconPActive from 'assets/stepsnew/3ps.svg';\n\nimport step4Icon from 'assets/stepsnew/4n.svg';\nimport step4IconActive from 'assets/stepsnew/4s.svg';\nimport step4IconPassed from 'assets/stepsnew/4p.svg';\nimport step4IconPActive from 'assets/stepsnew/4ps.svg';\n\nimport step5Icon from 'assets/stepsnew/5n.svg';\nimport step5IconActive from 'assets/stepsnew/5s.svg';\nimport step5IconPassed from 'assets/stepsnew/5p.svg';\nimport step5IconPActive from 'assets/stepsnew/5ps.svg';\n\nimport step6Icon from 'assets/stepsnew/6n.svg';\nimport step6IconActive from 'assets/stepsnew/6s.svg';\nimport step6IconPassed from 'assets/stepsnew/6p.svg';\nimport step6IconPActive from 'assets/stepsnew/6ps.svg';\n\nimport step7Icon from 'assets/stepsnew/7n.svg';\nimport step7IconActive from 'assets/stepsnew/7s.svg';\nimport step7IconPassed from 'assets/stepsnew/7p.svg';\nimport step7IconPActive from 'assets/stepsnew/7ps.svg';\n\nimport step8Icon from 'assets/stepsnew/8n.svg';\nimport step8IconActive from 'assets/stepsnew/8s.svg';\nimport step8IconPassed from 'assets/stepsnew/8p.svg';\nimport step8IconPActive from 'assets/stepsnew/8ps.svg';\n\nimport step9Icon from 'assets/stepsnew/9n.svg';\nimport step9IconActive from 'assets/stepsnew/9s.svg';\nimport step9IconPassed from 'assets/stepsnew/9p.svg';\nimport step9IconPActive from 'assets/stepsnew/9ps.svg';\n\nimport step11Icon from 'assets/stepsnew/11n.svg';\nimport step11IconActive from 'assets/stepsnew/11s.svg';\nimport step11IconPassed from 'assets/stepsnew/11p.svg';\nimport step11IconPActive from 'assets/stepsnew/11ps.svg';\n\n// import step10Icon from 'assets/steps/11p.svg';\n// import step10IconActive from 'assets/steps/11a.svg';\nimport step12Icon from 'assets/stepsnew/12n.svg';\nimport step12IconActive from 'assets/stepsnew/12s.svg';\nimport step12IconPassed from 'assets/stepsnew/12p.svg';\nimport step12IconPActive from 'assets/stepsnew/12ps.svg';\n\nimport heatmapIcon from 'assets/stepsnew/heatmapn.svg';\nimport heatmapIconActive from 'assets/stepsnew/heatmaps.svg';\nimport heatmapIconPassed from 'assets/stepsnew/heatmaps.svg';\nimport heatmapIconPActive from 'assets/stepsnew/heatmaps.svg';\n\nimport tool1Icon from 'assets/tools/1p.svg';\nimport tool1IconActive from 'assets/tools/1a.svg';\nimport tool2Icon from 'assets/tools/2p.svg';\nimport tool2IconActive from 'assets/tools/2a.svg';\n// import tool3Icon from 'assets/tools/3p.svg';\n// import tool3IconActive from 'assets/tools/3a.svg';\nimport tool4Icon from 'assets/tools/4p.svg';\nimport tool4IconActive from 'assets/tools/4a.svg';\nimport tool5Icon from 'assets/tools/5p.svg';\nimport tool5IconActive from 'assets/tools/5a.svg';\nimport tool6Icon from 'assets/tools/6p.svg';\nimport tool6IconActive from 'assets/tools/6a.svg';\nimport tool7Icon from 'assets/tools/7p.svg';\nimport tool7IconActive from 'assets/tools/7a.svg';\nimport tool8Icon from 'assets/tools/8p.svg';\nimport tool8IconActive from 'assets/tools/8a.svg';\nimport tool9Icon from 'assets/tools/9p.svg';\nimport tool9IconActive from 'assets/tools/9a.svg';\nimport tool10Icon from 'assets/tools/10p.svg';\nimport tool10IconActive from 'assets/tools/10p.svg';\n\nimport { IconGenuineCross, IconGenuineFlake, IconGenuineTriangle } from 'shared/components/Icons';\n\nexport const genuieShapes = [\n { icon: IconGenuineTriangle, shape: 2 },\n { icon: IconGenuineCross, shape: 1 },\n { icon: IconGenuineFlake, shape: 3 },\n];\n\nexport const steps = [\n {\n id: 1,\n name: '2D CC/MLO Views',\n icon: step1Icon,\n iconActive: step1IconActive,\n iconPassed: step1IconPassed,\n iconPassedActive: step1IconPActive,\n views: {\n RCC: {\n active: true,\n position: 'right',\n thumbpos: 'left',\n key: 'RCC',\n has_switcher: true,\n dataType: '2D',\n },\n LCC: {\n active: true,\n position: 'left',\n thumbpos: 'right',\n key: 'LCC',\n has_switcher: true,\n dataType: '2D',\n },\n RMLO: {\n active: true,\n position: 'right',\n thumbpos: 'left',\n key: 'RMLO',\n has_switcher: true,\n dataType: '2D',\n },\n LMLO: {\n active: true,\n position: 'left',\n thumbpos: 'right',\n key: 'LMLO',\n has_switcher: true,\n dataType: '2D',\n },\n },\n },\n {\n id: 2,\n name: '2D CC Views',\n icon: step2Icon,\n iconActive: step2IconActive,\n iconPassed: step2IconPassed,\n iconPassedActive: step2IconPActive,\n views: {\n RCC: {\n active: true,\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '2D',\n key: 'RCC',\n },\n LCC: {\n active: true,\n position: 'left',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '2D',\n key: 'LCC',\n },\n },\n },\n {\n id: 3,\n name: '2D MLO Views',\n icon: step3Icon,\n iconActive: step3IconActive,\n iconPassed: step3IconPassed,\n iconPassedActive: step3IconPActive,\n views: {\n RMLO: {\n active: true,\n key: 'RMLO',\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '2D',\n },\n LMLO: {\n active: true,\n key: 'LMLO',\n position: 'left',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '2D',\n },\n },\n },\n {\n id: 4,\n name: '2D/3D RCC Views',\n icon: step4Icon,\n iconActive: step4IconActive,\n iconPassed: step4IconPassed,\n iconPassedActive: step4IconPActive,\n views: {\n RCC: {\n active: true,\n key: 'RCC',\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '2D',\n },\n RCC_L: {\n active: true,\n key: 'RCC',\n layers: true,\n position: 'left',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '3DQ',\n },\n },\n },\n {\n id: 5,\n name: '2D/3D RMLO Views',\n icon: step5Icon,\n iconActive: step5IconActive,\n iconPassed: step5IconPassed,\n iconPassedActive: step5IconPActive,\n views: {\n RMLO: {\n active: true,\n key: 'RMLO',\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '2D',\n },\n RMLO_L: {\n active: true,\n key: 'RMLO',\n layers: true,\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '3DQ',\n },\n },\n },\n {\n id: 6,\n name: '2D/3D LCC Views',\n icon: step6Icon,\n iconActive: step6IconActive,\n iconPassed: step6IconPassed,\n iconPassedActive: step6IconPActive,\n views: {\n LCC: {\n active: true,\n key: 'LCC',\n position: 'right',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '2D',\n },\n LCC_L: {\n active: true,\n key: 'LCC',\n layers: true,\n position: 'left',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '3DQ',\n },\n },\n },\n {\n id: 7,\n name: '2D/3D LMLO Views',\n icon: step7Icon,\n iconActive: step7IconActive,\n iconPassed: step7IconPassed,\n iconPassedActive: step7IconPActive,\n views: {\n LMLO: {\n active: true,\n key: 'LMLO',\n position: 'right',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '2D',\n },\n LMLO_L: {\n active: true,\n key: 'LMLO',\n layers: true,\n position: 'left',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '3DQ',\n },\n },\n },\n {\n id: 8,\n name: '2D/3D stacked R CC/MLO, Toggle (T) between modes',\n icon: step8Icon,\n iconActive: step8IconActive,\n iconPassed: step8IconPassed,\n iconPassedActive: step8IconPActive,\n views: {\n RCC: {\n active: true,\n key: 'RCC',\n layers: true,\n has_switcher: true,\n dataType: '2D',\n position: 'left',\n thumbpos: 'left',\n },\n RMLO: {\n active: true,\n key: 'RMLO',\n layers: true,\n has_switcher: true,\n dataType: '2D',\n position: 'left',\n thumbpos: 'left',\n },\n },\n },\n {\n id: 9,\n name: '2D/3D stacked L CC/MLO, Toggle (T) between modes',\n icon: step9Icon,\n iconActive: step9IconActive,\n iconPassed: step9IconPassed,\n iconPassedActive: step9IconPActive,\n views: {\n LMLO: {\n active: true,\n key: 'LMLO',\n layers: true,\n has_switcher: true,\n dataType: '2D',\n position: 'right',\n thumbpos: 'right',\n },\n LCC: {\n active: true,\n key: 'LCC',\n layers: true,\n has_switcher: true,\n dataType: '2D',\n position: 'left',\n thumbpos: 'right',\n },\n },\n },\n {\n id: 11,\n name: 'question',\n icon: step11Icon,\n iconActive: step11IconActive,\n iconPassed: step11IconPassed,\n iconPassedActive: step11IconPActive,\n type: 'modal',\n views: {},\n disabled_no_active: 9,\n },\n {\n id: 12,\n name: 'heatmap',\n icon: heatmapIcon,\n iconActive: heatmapIconActive,\n iconPassed: heatmapIconPassed,\n iconPassedActive: heatmapIconPActive,\n type: 'modal',\n views: {},\n disabled_no_active: 10,\n },\n {\n id: 13,\n name: 'explanator',\n icon: step12Icon,\n iconActive: step12IconActive,\n iconPassed: step12IconPassed,\n iconPassedActive: step12IconPActive,\n type: 'modal',\n views: {},\n disabled_no_active: 10,\n },\n];\n\nexport const tools = [\n {\n name: 'FT',\n title: 'Show/Hide Finding Tool',\n icon: tool1Icon,\n iconActive: tool1IconActive,\n type: 'findingToggle',\n cursor: 'default',\n height: 23,\n onTimeAction: true,\n canStayActive: true,\n noResetFindings: true,\n },\n {\n name: 'F',\n title: 'Activate Finding Tool',\n icon: tool2Icon,\n iconActive: tool2IconActive,\n type: 'finding',\n cursor: 'arrow',\n height: 23,\n onTimeAction: false,\n },\n {\n name: 'FR',\n title: 'Reset Finding Tool',\n icon: tool10Icon,\n iconActive: tool10IconActive,\n type: 'findingReset',\n cursor: 'default',\n height: 23,\n onTimeAction: true,\n },\n {\n type: 'separator',\n },\n {\n name: 'R',\n title: 'Reset Viewers',\n icon: tool4Icon,\n iconActive: tool4IconActive,\n type: 'reset',\n cursor: 'default',\n onTimeAction: true,\n },\n {\n name: 'P',\n title: 'Activate Panning Tool',\n icon: tool5Icon,\n iconActive: tool5IconActive,\n type: 'pan',\n cursor: 'pan',\n onTimeAction: false,\n },\n {\n name: 'I Z',\n title: 'Activate Interactive Zoom',\n icon: tool6Icon,\n iconActive: tool6IconActive,\n type: 'zoomInteractive',\n cursor: 'izoom',\n onTimeAction: false,\n },\n {\n name: 'Z 1:1',\n title: 'Activate 1:1 Tool',\n icon: tool7Icon,\n iconActive: tool7IconActive,\n type: 'zoom1_1',\n cursor: 'fullzoom',\n onTimeAction: false,\n },\n {\n name: 'M',\n title: 'Activate Magnifier Tool',\n icon: tool8Icon,\n iconActive: tool8IconActive,\n type: 'zoomMagnifier',\n cursor: 'mzoom',\n onTimeAction: false,\n },\n {\n name: 'W/L',\n title:\n 'Activate Window/Level Tool (W; for presets move mouse over viewer and press numeric keys 1, 2, 3, ...)',\n icon: tool9Icon,\n iconActive: tool9IconActive,\n type: 'windowLevel',\n cursor: 'wlevel',\n onTimeAction: false,\n },\n];\n\nexport const getToolByType = type => {\n return tools.find(item => item.type === type);\n};\n\nexport const getDefaultType = (type, datas) => {\n if (type === '3DQ' && !datas['3DQ'] && datas['tomo']) return 'tomo';\n return type;\n};\n","export default __webpack_public_path__ + \"static/media/2p.1d75b871.svg\";","export default __webpack_public_path__ + \"static/media/4a.8302ecaa.svg\";","import React, { useRef } from 'react';\nimport { openFullscreen, closeFullscreen } from 'utils/appHelpers';\n\nimport iconMax from 'assets/arrows/2p.svg';\nimport iconMin from 'assets/arrows/4a.svg';\nimport BackButton from 'shared/BackButton';\nimport useOutsideClick from 'shared/hooks/useOutsideClick';\nimport { saveToStore, getFromStore } from 'utils/storeHelpers';\n\nconst ControlArea = ({\n tools,\n course,\n fullScreen,\n activeTool,\n setActiveTool,\n history,\n viewerOptions,\n disabled,\n}) => {\n const toolClickHandle = item => {\n const state =\n item.type === activeTool.type && !item.onTimeAction ? { type: 'none' } : { ...item };\n setActiveTool(state);\n };\n\n const tooltipRef = useRef();\n\n useOutsideClick(tooltipRef, () => tooltipRef.current.classList.remove('hover'));\n\n return (\n \n
\n
\n {!fullScreen && history &&
}\n
{\n tooltipRef.current.classList.remove('hover');\n saveToStore('viewerToolTip', true);\n }}\n >\n \n e.stopPropagation()}\n >\n {fullScreen ? 'Normal ' : 'Full '}Screen \n \n Click this button to\n {fullScreen ? ' turn off the full screen' : ' display the viewer in full screen'}\n \n \n {course &&
{course.title}
}\n
\n
\n
\n
\n {tools.map(item => {\n const isSeparator = item.type === 'separator';\n const canActive = viewerOptions && viewerOptions.canBeActive[item.type];\n const isActive = (activeTool.type === item.type && !item.onTimeAction) || canActive;\n if (isSeparator) return
;\n return (\n
\n {item.icon && (\n \n )}\n \n );\n })}\n
\n
\n
\n
\n
\n );\n};\n\nexport default ControlArea;\n","export default __webpack_public_path__ + \"static/media/caseLeft.2fe26188.svg\";","export default __webpack_public_path__ + \"static/media/caseRight.a83a8fb9.svg\";","import React from 'react';\nimport CaseLeftIcon from 'assets/arrows/caseLeft.svg';\nimport CaseRightIcon from 'assets/arrows/caseRight.svg';\n\nconst Switcher = ({ name, previews, next, disabled, current, allCount, highlightNext }) => {\n return (\n \n
\n \n \n
\n {name} {current + 1} of {allCount}\n
\n
allCount || disabled}\n >\n \n \n
\n );\n};\n\nexport default Switcher;\n","export default __webpack_public_path__ + \"static/media/3p.a659a691.svg\";","export default __webpack_public_path__ + \"static/media/stepLeft.aa8b0a8b.svg\";","export default __webpack_public_path__ + \"static/media/stepRight.143feca5.svg\";","import React, { useEffect } from 'react';\nimport Switcher from './Switcher';\nimport { IconAbout } from 'shared/components/Icons';\nimport checkIcon from 'assets/arrows/3p.svg';\nimport StepLeftIcon from 'assets/arrows/stepLeft.svg';\nimport StepRightIcon from 'assets/arrows/stepRight.svg';\n\nconst StepsArea = ({\n cases,\n steps,\n completedSteps,\n activeCase,\n activeStep,\n changeActiveCase,\n changeActiveStep,\n setActiveStep,\n onHelpModalOpen,\n}) => {\n const isLastCase = activeCase === cases.length - 1;\n const isLastStep = activeStep === steps.length - 1;\n const highlightNextCase = isLastStep && !isLastCase;\n\n const handleStepChange = bool => {\n setActiveStep(current => {\n const disabledPrev = current - 1 < 0;\n const disabledNext = Number(current) + 2 > steps.length;\n if ((bool && !disabledNext) || (!bool && !disabledPrev)) {\n changeActiveStep(current - (bool ? -1 : 1));\n }\n if (bool && disabledNext && !isLastCase) {\n handleCaseChange(true);\n }\n return current;\n });\n };\n\n const handleCaseChange = bool => {\n changeActiveCase(activeCase - (bool ? -1 : 1));\n changeActiveStep(0);\n };\n\n const disabledPrev = () => {\n return activeStep - 1 < 0;\n };\n\n // const goToFeedback = () => {\n // history.push(getFeedbackUrl(reduxCourse));\n // };\n\n const handleUserKeyPress = event => {\n const { keyCode } = event;\n if (keyCode === 37) handleStepChange(false);\n if (keyCode === 39) handleStepChange(true);\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleUserKeyPress);\n return () => {\n window.removeEventListener('keydown', handleUserKeyPress);\n };\n //eslint-disable-next-line\n }, []);\n\n return (\n \n
\n onHelpModalOpen(true)}>\n \n Help\n \n
\n
\n
\n \n \n {steps.map((item, index) => {\n const isActive = completedSteps.includes(index);\n const isExplanator = item.name === 'explanator';\n // const isHeatmap = item.name === 'heatmap';\n const isNeedToDone =\n activeStep >= 9 &&\n completedSteps.length < 10 &&\n !isActive &&\n activeStep !== index &&\n !isExplanator;\n return (\n
changeActiveStep(index)}\n >\n {isActive && }\n {item.icon && (\n \n )}\n \n );\n })}\n {/*
goToFeedback()}>\n \n */}\n
steps.length}\n >\n \n \n
\n
\n
\n );\n};\n\nexport default StepsArea;\n","export default __webpack_public_path__ + \"static/media/4p.52d5b854.svg\";","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMYAAABKCAYAAADg6Sv+AAAMJ2lDQ1BJQ0MgUHJvZmlsZQAASImVlwdUk8kWgOcvSUhIaIEISAm9idKr9BpAQKpgIySBhBJCQlCxI4sKrgUVUazIioiCawFksWEvi2DvDwsqyrpYsKHyJgmgq+e9d949Z/7/y507d+69mX/ODACq0WyRKAtVAyBbmCeOCfFnTkpKZpK6AQJwoAZUgDmbIxH5RUdHACjD73/Ku+vQGsoVW5mvn/v/q6hzeRIOAEg05FSuhJMN+QAAuAtHJM4DgNAL9SYz8kSQiTBKoCmGAUI2lXG6gt1knKrgCLlNXEwA5BQAlKhstjgdABVZXMx8Tjr0o7IMsp2QKxBCboXszeGzuZA/Qx6TnZ0DWdUSsmXqd37S/+EzdcQnm50+wopc5KIUKJCIstiz/s9y/G/JzpIOz2ECG5UvDo2R5SyrW2ZOuIypkM8KUyOjIGtAvirgyu1l/IQvDY0fsv/AkQTAmgEGACiVyw4Mh6wH2ViYFRkxpPdOEwSzIMPao3GCPFacYizKFefEDPlHZ/IkQbHDzBbL55LZlEgz4/2GfG7m81jDPlsK+HGJijjRjnxBQiRkFch3JZmx4UM2zwv4AZHDNmJpjCxm+J9jIE0cHKOwwUyzJcN5YR58AStyiCPy+HGhirHYNA5bHps25AyeZFLEcJxcXmCQIi+skCeMH4ofKxPl+ccM2VeLsqKH7LFWXlaITG8MuV2SHzs8ti8PLjZFvjgQ5UXHKWLDNTPYYdGKGHBrEAECQCBgAilsqSAHZABBe29TL/yl6AkGbCAG6YAHbIc0wyMS5T1C+IwFBeAvSDwgGRnnL+/lgXyo/zKiVTxtQZq8N18+IhM8gZwNwkEW/C2VjxKOzJYAHkON4KfZOTDWLNhkfT/pmKrDOmIQMZAYSgwmWuG6uDfuiUfApy9sDrgb7j4c1zd7whNCJ+Eh4Rqhi3BruqBQ/EPkTDABdMEYg4eyS/0+O9wcenXG/XEv6B/6xhm4LrDFneBMfrgPnNsZar+PVTqS8bdaDvki25FR8iiyL9nyxwhUrFWcR7zIKvV9LRRxpY5UK2Ck58c8Ar6rHxe+w3+0xJZg+7Ez2HHsHNaKNQEmdhRrxi5ih2U8sjYey9fG8Gwx8ngyoR/BT/Oxh+aUVU1iV2fXY/d5qA/k8WbmyT6WgBzRLLEgnZ/H9IO7NY/JEnLGjmE62DnYAyDb+xVbyxuGfE9HGOe/6XKPAeBeApXp33RsuAcdegIA/d03nclruOxXAnC4gyMV5yt0uOxBABSgCr8UHWAA9y5LmJEDcAGewBcEgTAQBeJAEpgG68yH61QMZoA5YCEoBqVgJVgLNoAtYDvYCfaAfaAJtILj4DS4ADrANXAHrpVu8AL0gXdgAEEQEkJD6IgOYoiYITaIA+KGeCNBSAQSgyQhKUg6IkSkyBxkEVKKlCEbkG1ILfI7cgg5jpxDOpFbyAOkB3mNfEIxlIpqovqoOToOdUP90HA0Dp2KpqO5aAFahC5HK9AqdDfaiB5HL6DX0C70BdqPAUwZY2BGmC3mhgVgUVgyloaJsXlYCVaOVWH1WAv8p69gXVgv9hEn4nScidvC9RqKx+McPBefhy/DN+A78Ub8JH4Ff4D34V8JNIIewYbgQWARJhHSCTMIxYRywg7CQcIp+O10E94RiUQG0YLoCr+9JGIGcTZxGXETsYF4jNhJfETsJ5FIOiQbkhcpisQm5ZGKSetJu0lHSZdJ3aQPSspKhkoOSsFKyUpCpUKlcqVdSkeULis9VRogq5HNyB7kKDKXPIu8glxNbiFfIneTByjqFAuKFyWOkkFZSKmg1FNOUe5S3igrKxsruytPVBYoL1CuUN6rfFb5gfJHqgbVmhpAnUKVUpdTa6jHqLeob2g0mjnNl5ZMy6Mtp9XSTtDu0z6o0FXGqrBUuCrzVSpVGlUuq7xUJauaqfqpTlMtUC1X3a96SbVXjaxmrhagxlabp1apdkjthlq/Ol3dXj1KPVt9mfou9XPqzzRIGuYaQRpcjSKN7RonNB7RMboJPYDOoS+iV9NP0bs1iZoWmizNDM1SzT2a7Zp9WhpaTloJWjO1KrUOa3UxMIY5g8XIYqxg7GNcZ3wapT/KbxRv1NJR9aMuj3qvPVrbV5unXaLdoH1N+5MOUydIJ1NnlU6Tzj1dXNdad6LuDN3Nuqd0e0drjvYczRldMnrf6Nt6qJ61XozebL3tehf1+vUN9EP0Rfrr9U/o9xowDHwNMgzWGBwx6DGkG3obCgzXGB41fM7UYvoxs5gVzJPMPiM9o1AjqdE2o3ajAWML43jjQuMG43smFBM3kzSTNSZtJn2mhqYTTOeY1pneNiObuZnxzdaZnTF7b25hnmi+2LzJ/JmFtgXLosCizuKuJc3SxzLXssryqhXRys0q02qTVYc1au1szbeutL5kg9q42AhsNtl0jiGMcR8jHFM15oYt1dbPNt+2zvbBWMbYiLGFY5vGvhxnOi553KpxZ8Z9tXO2y7Krtrtjr2EfZl9o32L/2sHageNQ6XDVkeYY7DjfsdnxlZONE89ps9NNZ7rzBOfFzm3OX1xcXcQu9S49rqauKa4bXW+4abpFuy1zO+tOcPd3n+/e6v7Rw8Ujz2Ofx9+etp6Znrs8n423GM8bXz3+kZexF9trm1eXN9M7xXurd5ePkQ/bp8rnoa+JL9d3h+9TPyu/DL/dfi/97fzF/gf93wd4BMwNOBaIBYYElgS2B2kExQdtCLofbBycHlwX3BfiHDI75FgoITQ8dFXoDZY+i8OqZfWFuYbNDTsZTg2PDd8Q/jDCOkIc0TIBnRA2YfWEu5FmkcLIpigQxYpaHXUv2iI6N/qPicSJ0RMrJz6JsY+ZE3Mmlh47PXZX7Ls4/7gVcXfiLeOl8W0JqglTEmoT3icGJpYldk0aN2nupAtJukmCpOZkUnJC8o7k/slBk9dO7p7iPKV4yvWpFlNnTj03TXda1rTD01Wns6fvTyGkJKbsSvnMjmJXsftTWakbU/s4AZx1nBdcX+4abg/Pi1fGe5rmlVaW9izdK311eg/fh1/O7xUECDYIXmWEZmzJeJ8ZlVmTOZiVmNWQrZSdkn1IqCHMFJ7MMciZmdMpshEVi7pyPXLX5vaJw8U7JIhkqqQ5TxMesi9KLaW/SB/ke+dX5n+YkTBj/0z1mcKZF2dZz1o662lBcMFvs/HZnNltc4zmLJzzYK7f3G3zkHmp89rmm8wvmt+9IGTBzoWUhZkL/yy0KywrfLsocVFLkX7RgqJHv4T8UlesUiwuvrHYc/GWJfgSwZL2pY5L1y/9WsItOV9qV1pe+nkZZ9n5X+1/rfh1cHna8vYVLis2rySuFK68vspn1c4y9bKCskerJ6xuXMNcU7Lm7drpa8+VO5VvWUdZJ13XVRFR0bzedP3K9Z838Ddcq/SvbNiot3HpxvebuJsub/bdXL9Ff0vplk9bBVtvbgvZ1lhlXlW+nbg9f/uT6oTqM7+5/Va7Q3dH6Y4vNcKarp0xO0/WutbW7tLbtaIOrZPW9eyesrtjT+Ce5nrb+m0NjIbSvWCvdO/z31N+v74vfF/bfrf99QfMDmw8SD9Y0og0zmrsa+I3dTUnNXceCjvU1uLZcvCPsX/UtBq1Vh7WOrziCOVI0ZHBowVH+4+JjvUeTz/+qG16250Tk05cPTnxZPup8FNnTwefPnHG78zRs15nW895nDt03u180wWXC40XnS8e/NP5z4PtLu2Nl1wvNXe4d7R0ju88ctnn8vErgVdOX2VdvXAt8lrn9fjrN29MudF1k3vz2a2sW69u598euLPgLuFuyT21e+X39e5X/cvqXw1dLl2HHwQ+uPgw9uGdR5xHLx5LHn/uLnpCe1L+1PBp7TOHZ609wT0dzyc/734hejHQW/yX+l8bX1q+PPC3798X+yb1db8Svxp8veyNzpuat05v2/qj+++/y3438L7kg86HnR/dPp75lPjp6cCMz6TPFV+svrR8Df96dzB7cFDEFrPlRwEMNjQtDYDXNQDQkuDZoQMAymTF3UwuiOI+KSfwn1hxf5OLCwA1vgDELwAgAp5RNsNmBpkK37IjeJwvQB0dR9qQSNIcHRS+qPDGQvgwOPhGHwBSCwBfxIODA5sGB79Uw2BvAXAsV3EnlInsDrrVWkaXxlM+gB/k31JEcIw/+dkJAAAACXBIWXMAABYlAAAWJQFJUiTwAAABnGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xOTg8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+NzQ8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KkcLNnQAAABxpRE9UAAAAAgAAAAAAAAAlAAAAKAAAACUAAAAlAAAFL/bhx+cAAAT7SURBVHgB7JzLi1RHFMa/qoYRNyoSTMeFTyKRhEQSFy50oYLPhYoQEoWQGLJRhMBkk6ArF/4B2QiB6ELFJ+NbZiROEkVHUVHjJBMdNBESk5CYTRSD3FtWVdvXmbk1fbp7uuaexbmLofv0rarv+7p+dR/cHlUulw1kkwQkgUEJKAFjUB7yRhLwCQgYMhEkgUACAkYgFClJAgKGzAFJIJCAgBEIRUqSgIAhc0ASCCQgYARCkZIkUAgY6YJF7JLX586y0zRaguT7yCddCBjJ59vySgqsqPv3oPd8XaCCYoeW7yOff2FguMmI+7/kFY1yxcxfCAFjm89Avo8Xk69QMDis0m61FDD4ZMDl+xAwBAxwmYxuveaiRcAQMNhMRgGD0WTkskK9OLsd/VecMuCiRY4YjCAdfSQqI3KZjE4NFy0ChoDBZjIKGIwmI5cVqrJ2F/OXUwZctMgRgxGkxWDB5/RFjhiMJiOXFaooKDhNRk5a5IjBCNKi4OC0OHDRImAIGHLxHViRooFhps6A+vVuYEg5pw2GUmCRyyrtIqC0mPJkqD9+j55WFDDSxcug7vZD3esPGqDMBxtFKnLSEski2S2nDCgt6foNUOe77aJrH0KNuLUcjHTlGpg334Y+uBuq/+egdMp8sFGkIictkSyS3XLKgNLiwDATJkJ3nYS68xPprdkdWgpGunYdzKzZXos+vBfqdlg4Zb5ZM82046SlGf2taMMpA0qLB2PKdOD/JxU4bl1vRQS5PloGRib477+AlyZBd+yD6uvNDegKlPlgo0hFTloiWSS75ZQBpSWbZ86VMdBn7JHj6iXSY6M7tASM5ONNwKQyYKFwMLgf/+ijB6B+/CGohzIfbBSpyElLJItkt5wyoLRUwdBnO5EuWuq9qe/OQF/4nvTZyA4jBiPZ9BkwbryHQnfsh5n9RgWM44ehhjnMUeYbMTDSfTlpGamXZttzyoDSUgWjtH0rzMxXkb77gbetLp6D/rar2Qhy7UYERtK+FWhr81CU9u4EHv0H98N6f8Q42QF181puQFegzAcbRSpy0hLJItktpwwoLQPBcMaMPW1PP9nsPaprl6E7j5N+69mhOTDaxiBp31Lp354+lb76MhsrA+P0UajrV7L6wBeU+YH7xn7NSUtsr8P1zykDSstQMLynsWORfPqFf6l6b0AfOzSc1brrjYMxfgKSje2VAR78htKuHYMGy8Cw5DqCQxtlPtQmVo2TllgeqX45ZUBpCYLx3KCHw0Ki7vT5a1w8fUpZH/bzhsAwL7+CdMNG35nqu2XvPO3PdZyB4e4WXOnJfe4KlPlgo0hFTloiWSS75ZQBpaUWGM5o4k6r7OmVsv+BRh+x89Oe3jez1Q2Ge8QjXfeRH0P12Aud7vCFTgbGN6ehLl8IaqLMBxtFKnLSEski2S2nDCgtFBjObPr+hzDTZgJ/PkDJwfHwHzKDoTvUBYZ57XWka97zbXXXiZr3jTMwujuhes4PHc+/p8wHG0UqctISySLZLacMKC31gOEMpytWw7z1DvDvQ3/kaPT5KhIMM2cu0uWrfLj61BGoG1drBl0Fo9a9Zcp8zQFa/CEnLS22Vnd3nDKgtNQLhjNfnYt4/KgCRwPPV9UEw8xbgHThEh+wPnYQqvcmGXZVjLL/C1bbh71CG2U+1CZWjZOWWB6pfjllQGlpBAznuzofkSSVpzHshXk92zMAAAD//0Y89bwAAAU5SURBVO1cWY9URRg9VUYCmjgYURuigmgkbrhgQkjQKAkYCYpLDDE+aEh8NPFVE5944F/4oJkojjIsspjRCTMGUTMhOEFEQYYlRgkx8oBrmCqrbts9TU/1/W733Jp7Hqpe5k4tX51z6jt3qdvdqlarWXQoZtVjsI+vzVr1nkGo8cMdek5Vm0fXwK5+Amr0c+iDB6YaWo4m39wCdXYCuv+dltpqDpmwVKMAwKSBhMW8vBn2tttx1da3C8ll1jwJu3I1cPky9OA2qJPHC41TecbwEeyKlTDrNmTB1MgQ9JejuYGbxjjg+h4K95XI505QciMTlpKpFQ7HpIGEpRtjmKdfgL3vQeDSJegdH0CdO1NYE9EYPpK9/yGYDc9nQdXYV9BDezpO0DCGHt4P9fXBYD+JfHBQpEomLJEoimGZNJCwFDWG2fQK7NI7gd8u1K8UF86LOrR2KGQMP8AuuwfmuU2A0lDHj2aTtQZqHDeNMbQXauxQo/qKvxL5KzpH/ocJS2SqHcMzaSBhKWKMRh/18znoXQPAxd87cu/UUNgYPoB3oNnozDF3LtSpE9Db3p0Wt2mMT3dDHf5mWruvkMgHB0WqZMISiaIYlkkDCUsj6Ts9Y0y+9jqw4Caon36E/mQ78OcfIv9Qh66M4QPYWxfDPPMicF1f8AG6aYx9O6GOjIXmTMYIqlJdpZSMs4lMwpJnjMk33gLmzYP6bhx672D2wN0r9q6N4SeytUXwDzaZM9t2l5rGyNnFksj3SqaXcUxYesFfxhgmDSQsQWM4M2SmcGL4uxTt7lZmWnoyhp/U3nAjrHsgt4tuqV85BvqBf/9B0xi7P4Y6eiSITyIfHBSpkglLJIpiWCYNJCztxrDutsn42ydXlNsF1W43tIzSszGyyfvmw6x/FnbJHXVzuHs6s/zh7D2G3jkAdWw8iFEiHxwUqZIJSySKYlgmDSQsrcbweWdeejXjp0Y+c68SRkSuRTvMzBh+lmuuhXlqI+xdd2fmwNnTdWP4lylu9ypUJPKhMbHqmLDE4ijFZdJAwtIwhnbPsD7vfNE5O6AS907tMzeGj3z1nLo57l0OnP8FuHkh9Pb3oX44FpxXIh8cFKmSCUskimJYJg0kLA1jZKSshd63A+pb+RMZoghtHcoxhg+qVN0cD6zIptAf9UOdCL9+l8i3YYz6LxOWqERzgjNpIGFpGuPvv6D374L6PnxXkkO3UFN5xvh/OrN2Pewjq6A/fC/bSw6hkMiHxsSqY8ISi6MUl0kDCUtmjL7rs50n/64iVindGB6ocR88VGcmoCZOBnFL5IODIlUyYYlEUQzLpIGExRtDjQ67zz2dFnnNpEMUY3hAdvFSZ45TQWwS+eCgSJVMWCJRFMMyaSBhsbWFUL+659jIJZox8nBL5PPGlt3GhKVsbkXjMWnAgiUZg+i7IUUTuex+LMnoebFgScZIxqBJxmQMomRkOUOVfRXoJh6TBixY0hWDyKTdJHOZfVmS0XNiwZKMkYxBk4zJGETJyHKGKvMK0G0sJg1YsKQrBpFJu03osvqzJKPnw4IlGSMZgyYZkzGIkpHlDFXW2b+XOEwasGBJVwwik/aS1GWMYUlGz4UFS6XG8F9qqrpkv5rY9r31qjHN9vyNZEzrMaV8ZcaYglD9EcvPhValhDcGU2FYj0qM4X8wga3oL4bZIM0anrQe06WuxBjTYaSapACXAskYXOuR0JAokIxBshAJBpcCyRhc65HQkCiQjEGyEAkGlwLJGFzrkdCQKJCMQbIQCQaXAskYXOuR0JAo8B/hofErSbwfsAAAAABJRU5ErkJggg==\"","export default __webpack_public_path__ + \"static/media/casechange.651fb364.png\";","import React from 'react';\n\nimport iconMagnifier from 'assets/tools/8p.svg';\nimport iconToggle from 'assets/tools/7p.svg';\nimport iconInteractiveZoom from 'assets/tools/6p.svg';\nimport iconMove from 'assets/tools/5p.svg';\nimport iconReset from 'assets/tools/4p.svg';\nimport iconBrightness from 'assets/tools/9p.svg';\nimport iconFinding from 'assets/tools/2p.svg';\nimport iconMaximize from 'assets/arrows/2p.svg';\nimport iconMinimize from 'assets/arrows/4p.svg';\nimport iconStudyOverview from 'assets/stepsnew/1n.svg';\nimport iconCC from 'assets/stepsnew/2n.svg';\nimport iconMLO from 'assets/stepsnew/3n.svg';\nimport iconCCRight2D from 'assets/stepsnew/4n.svg';\nimport iconMLORight2D from 'assets/stepsnew/5n.svg';\nimport iconCCLeft2D from 'assets/stepsnew/6n.svg';\nimport iconMLOLeft2D from 'assets/stepsnew/7n.svg';\nimport iconRightBrest from 'assets/stepsnew/8n.svg';\nimport iconLeftBrest from 'assets/stepsnew/9n.svg';\nimport iconAssessment from 'assets/stepsnew/11n.svg';\nimport iconDiagnostic from 'assets/stepsnew/heatmapn.svg';\nimport iconComments from 'assets/stepsnew/12n.svg';\n\n// Temporary Icons\n// import iconStructuredRoaming from 'assets/temp_icons/icon_quadrant_roaming.png';\n// import iconInvert from 'assets/tools/9p.svg';\n// import iconStack from 'assets/temp_icons/icon_stack.png';\nimport iconFindingsShow from 'assets/tools/1p.svg';\nimport iconFindingsDelete from 'assets/tools/10p.svg';\nimport iconSteps from 'assets/stepchange.png';\nimport iconCases from 'assets/casechange.png';\n// contact, print\n\nexport const viewerDialogJson = {\n zoomAndMagnificationTool: [\n {\n description: 'Magnifier',\n icon: iconMagnifier,\n buttons: [], // ['R'],\n },\n {\n description: 'Interactive zoom',\n icon: iconInteractiveZoom,\n buttons: [], // ['Z'],\n },\n {\n description:\n 'Full resolution mode: Clicking on the image will show in original full resolution. By clicking on it again, will toggle back to fit to viewer mode.',\n icon: iconToggle,\n buttons: [], // ['Shift', 'Z'],\n },\n {\n description: 'Maximize viewer',\n icon: iconMaximize,\n buttons: [], // ['Shift', 'M'],\n },\n // {\n // description:\n // 'Structured roaming: Moves through zoomed views to make sure the whole tissue is displayed.',\n // icon: iconStructuredRoaming,\n // buttons: [], // ['TAB'],\n // },\n {\n description: 'Panning/moving image (also via right mouse button)',\n icon: iconMove,\n buttons: [], // ['P'],\n },\n {\n description: 'Reset viewers: Changes zoom and window/level to initial values',\n icon: iconReset,\n buttons: [], // ['Shift', 'R'],\n },\n ],\n otherTools: [\n {\n description: 'Change window/level settings',\n icon: iconBrightness,\n buttons: [], // ['W'],\n },\n // {\n // description: 'Invert images',\n // icon: iconInvert,\n // buttons: [], // ['I'],\n // },\n // {\n // description: 'Stacking/Slicing (also via ↑ / ↓ or scroll wheel) ',\n // icon: iconStack,\n // buttons: [], // ['↑', '↓', 'S'],\n // },\n {\n description: 'Finding tool: Allows to place arrows at regions of interest.',\n icon: iconFinding,\n buttons: [], // ['F'],\n },\n {\n description: 'Show Markings',\n icon: iconFindingsShow,\n buttons: [], // ['Shift', 'F'],\n },\n {\n description: 'Delete Markings',\n icon: iconFindingsDelete,\n buttons: [], // ['Shift', 'F'],\n },\n ],\n navigation: [\n {\n description: 'Full screen on',\n icon: iconMaximize,\n buttons: [], // ['0'],\n },\n {\n description: 'Full screen off',\n icon: iconMinimize,\n buttons: [], // ['0', 'Esc'],\n isSlash: true,\n },\n {\n description:\n 'Hanging Protocol: Right (next) and left (prior) icons to step you through the images for that patient',\n icon: iconSteps,\n buttons: [], // ['←', '→'],\n width: '130px',\n isSlash: true,\n },\n {\n description:\n 'Case Navigation: Right (next) and left (prior) icons to navigate through the course',\n icon: iconCases,\n buttons: [],\n width: '130px',\n },\n ],\n hangingProtocols: [\n {\n className: 'col-4',\n childs: [\n {\n description: 'Study overview',\n icon: iconStudyOverview,\n buttons: [],\n width: '70px',\n },\n {\n description: 'CC Right/Left',\n icon: iconCC,\n buttons: [],\n },\n {\n description: 'MLO Right/Left',\n icon: iconMLO,\n buttons: [],\n },\n {\n description: 'CC Right 2D/Tomo',\n icon: iconCCRight2D,\n buttons: [],\n },\n {\n description: 'MLO Right 2D/Tomo',\n icon: iconMLORight2D,\n buttons: [],\n },\n {\n description: 'CC Left 2D/Tomo',\n icon: iconCCLeft2D,\n buttons: [],\n },\n {\n description: 'MLO Left 2D/Tomo',\n icon: iconMLOLeft2D,\n buttons: [],\n },\n ],\n },\n {\n className: 'flex-fill',\n childs: [\n {\n description: 'Right breast of interest',\n icon: iconRightBrest,\n buttons: [],\n },\n {\n description: 'Left breast of interest',\n icon: iconLeftBrest,\n buttons: [],\n },\n {\n description: 'Assessment (if available)',\n icon: iconAssessment,\n buttons: [],\n },\n {\n description: 'Expert’s findings and comments',\n icon: iconComments,\n buttons: [],\n },\n {\n description: 'Diagnostic images (if available)',\n icon: iconDiagnostic,\n buttons: [],\n },\n ],\n },\n ],\n caseAccessment: [\n Cases may be assessed only when all mandatory hangings were reviewed. ,\n \n Open the reporting page via click on\n \n or as part of the workflow.\n ,\n Note that you should report only the most suspicious finding per case. ,\n \n If \"Finding\" was selected, all corresponding subcategories need to be filled in before\n submission is possible.\n ,\n Submitted assessments cannot be changed afterwards. ,\n \n For comparison open experts' assessment after your assessment via{' '}\n .\n ,\n ],\n faq: [\n {\n q: 'What to do if I cannot open the survey?',\n a: '',\n variants: [\n 'Make sure that all previous course modules are completed and all case assessments were submitted (40 of 40)',\n 'Reload page (F5) if multiple tabs were used',\n ],\n },\n {\n q: 'What to do if my scrolling direction seems to be contra-intuitively for me?',\n a:\n 'This behavior is usually different on Windows PCs and Mac computers. If you are using a Mac, you may select the scroll direction as one of the mouse settings.',\n },\n {\n q: 'How to proceed if initial system tests fail?',\n a: 'Click on corresponding \"more\" links in the system test window for advice',\n },\n {\n q: 'How to reset a browser zoom? (e.g., if changed via Ctrl + scroll wheel)',\n a: 'Press (Ctrl + 0)',\n },\n {\n q: 'What to do if “exit fullscreen” annoys me in Chrome?',\n a: 'Use FireFox Version 31.0 or newer',\n },\n {\n q: 'What to do if IE11 fullscreen zero key does not work?',\n a: 'Use FireFox Version 31.0 or newer',\n },\n {\n q: 'What to do if in IE11 the mouse cursor for tools is not updated?',\n a: 'Use FireFox Version 31.0 or newer',\n },\n {\n q: 'How to control cine mode on tomo images?',\n a: 'Use context specific controlling options:',\n variants: [\n 'To start cine on all displayed tomo images: Click on the cine button of the toolbar',\n 'To start cine mode on one tomo image only: Use the context menu (via right mouse button)',\n 'Note: Space bar will start/stop cine mode in all viewers if the mouse cursor is outside of them, and in the focus viewer only, if the mouse cursor is within one of the viewers',\n ],\n },\n {\n q: 'What to do if volume control in firefox (31.0) does not work for videos?',\n a:\n 'Either use the volume control of the system in the lower right corner, or change the video volume control as usual, the volume will change, although the display of the volume control will not change. ',\n },\n ],\n};\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { zoomAndMagnificationTool } = viewerDialogJson;\n\nconst ZoomAndMagnificationTools = () => (\n \n
\n Generally, a double click into an image will magnify it-usually to the available size of the\n viewer. Mammograms\n are magnified to 1;1,i.e., original size.\n
\n {zoomAndMagnificationTool.map(({ icon, description, buttons, note }, index) => {\n return (\n
\n \n {description} \n {buttons.map((btn, index) => (\n {btn} \n ))}\n
\n );\n })}\n
\n);\n\nexport default ZoomAndMagnificationTools;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { otherTools } = viewerDialogJson;\n\nconst OtherTools = () => (\n \n {otherTools.map(({ icon, description, buttons }, index) => {\n return (\n
\n \n {description} \n {buttons.map((btn, index) => (\n {btn} \n ))}\n
\n );\n })}\n
\n);\n\nexport default OtherTools;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { navigation } = viewerDialogJson;\n\nconst Navigation = () => (\n \n {navigation.map(({ icon, description, buttons = [], isSlash, width }, index) => {\n return (\n
\n \n {description} \n {buttons.map((btn, index) => (\n \n {btn}\n \n ))}\n
\n );\n })}\n
\n);\n\nexport default Navigation;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { hangingProtocols } = viewerDialogJson;\n\nconst HangingProtocols = () => (\n \n
\n {hangingProtocols.map((group, groupIndex) => (\n
\n {group.childs.map(({ icon, description, buttons = [], isSlash, width }, childIndex) => {\n return (\n
\n \n {description} \n {buttons.map((btn, btnIndex) => (\n {btn} \n ))}\n
\n );\n })}\n
\n ))}\n
\n
\n Note : The current (screening) study is always displayed first.\n
\n
\n For hangings with viewers with multiple images: All images in a hanging can be toggled by\n clicking the tab icon.\n
\n
\n);\n\nexport default HangingProtocols;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { caseAccessment } = viewerDialogJson;\n\nconst CaseAccessment = () => {\n return (\n \n {caseAccessment.map((item, index) => (\n \n {item}\n \n ))}\n \n );\n};\n\nexport default CaseAccessment;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { faq } = viewerDialogJson;\n\nconst FAQ = () => {\n return (\n \n {faq.map(({ q, a, variants }, qIndex) => (\n
\n
\n Q: {q} \n
\n
A: {a}
\n {variants && variants.length > 0 && (\n
\n {variants.map((variant, vIndex) => (\n {variant} \n ))}\n \n )}\n
\n ))}\n
\n );\n};\n\nexport default FAQ;\n","/* eslint-disable react/jsx-no-duplicate-props */\n/* eslint-disable jsx-a11y/anchor-is-valid */\nimport { HOST } from 'configs';\nimport React from 'react';\n\nconst Print = () => {\n return (\n \n );\n};\n\nexport default Print;\n","import React, { useState } from 'react';\nimport Modal from 'react-modal';\nimport { AppBar, Tabs, Tab } from '@material-ui/core';\nimport {\n ZoomAndMagnificationTools,\n OtherTools,\n Navigation,\n HangingProtocols,\n CaseAccessment,\n // FAQ,\n Print,\n} from './components';\n\nconst modalStyles = {\n overlay: {\n // backgroundColor: '#000000',\n backgroundColor: 'rgba(0, 0, 0, 0.75)',\n },\n};\n\nconst tabs = [\n {\n label: 'Zoom and Magnification Tools',\n component: ,\n },\n {\n label: 'Other Tools',\n component: ,\n },\n {\n label: 'Navigation',\n component: ,\n },\n {\n label: 'Hanging Protocols',\n component: ,\n },\n {\n label: 'Case Accessment',\n component: ,\n },\n // {\n // label: 'FAQ',\n // component: ,\n // },\n {\n label: 'Print',\n component: ,\n },\n];\n\nconst HelpModal = ({ onModalClose }) => {\n const [tab, setTab] = useState(0);\n\n const handleTabChange = (e, index) => {\n setTab(index);\n };\n\n return (\n \n \n Radiologist Training - Online Help \n \n \n {tabs.map(({ label, component }, index) => (\n \n ))}\n \n \n {tabs[tab].component}
\n \n );\n};\n\nexport default HelpModal;\n","import React from 'react';\n\nconst getSmallerSize = imageWidth => {\n return Number((imageWidth / 50).toFixed());\n};\n\nconst Thumbnail = ({ viewport }) => {\n if (!viewport || !viewport.image) return null;\n const { scale, initialScale, translation, rotation, vflip, hflip } = viewport;\n const smallNum = getSmallerSize(viewport.image.width);\n const width = viewport.image.width / smallNum;\n const height = viewport.image.height / smallNum;\n const coreSize = {\n width: (initialScale * width) / scale,\n height: (initialScale * height) / scale,\n };\n const corePos = {\n transform: `translate(${-(translation.x / smallNum)}px, ${-(translation.y / smallNum)}px)`,\n };\n const imageStyle = {\n backgroundImage: `url('${viewport.image.imageId}')`,\n transform: `rotate(${rotation}deg) rotateX(${vflip ? 180 : 0}deg) rotateY(${\n hflip ? 180 : 0\n }deg)`,\n };\n if (coreSize.width > 100 || initialScale >= scale) return null;\n return (\n \n );\n};\n\nexport default Thumbnail;\n","export default __webpack_public_path__ + \"static/media/pause.a12d8b2f.svg\";","export default __webpack_public_path__ + \"static/media/speed.f967b66e.svg\";","import React, { useState } from 'react';\nimport ReactModal from 'react-modal';\nimport { IconClose } from 'shared/components/Icons';\n\nexport const getPrePostSliceItems = (index, pre, post) => {\n let prePostSlicesIndexes = [];\n if (post) {\n for (let i = index + 1; i <= index + post; i += 1) {\n prePostSlicesIndexes.push(i);\n }\n }\n if (pre) {\n for (let i = index - 1; i >= index - pre; i -= 1) {\n prePostSlicesIndexes.push(i);\n }\n }\n return { prePostSlicesIndexes };\n};\n\nexport const getImagesPrePostSlices = images => {\n let result = new Set();\n images.forEach((img, index) => {\n const hasGeniusShapes = !!img.geniusAIDataList?.length;\n if (!hasGeniusShapes) return;\n const { prePostSlicesIndexes } = getPrePostSliceItems(index, img.preSlices, img.postSlices);\n result = new Set([...result, ...prePostSlicesIndexes]);\n });\n return [...result];\n};\n\nexport const getPrimarySlices = images => {\n const result = new Set();\n images.forEach((img, index) => {\n const hasGeniusShapes = !!img.geniusAIDataList?.length;\n if (hasGeniusShapes || (hasGeniusShapes && (!!img.preSlices || !!img.postSlices))) {\n result.add(index);\n }\n });\n return [...result];\n};\n\nexport const getImagesSortedSlices = images => {\n const all = [];\n images.forEach((img, index) => {\n const hasGeniusShapes = !!img.geniusAIDataList?.length;\n if (!hasGeniusShapes) return;\n const { prePostSlicesIndexes } = getPrePostSliceItems(index - 1, img.preSlices, img.postSlices);\n all.push([...new Set([index, ...prePostSlicesIndexes])]);\n });\n return all;\n};\n\nconst PrePostSlices = ({ onSaveSlice, activeImage, imagesCount, playing }) => {\n const [activeTab, setActiveTab] = useState('preSlices');\n const [modalState, setModalState] = useState(false);\n const [imageData, setImageData] = useState();\n\n const onOpenModal = () => {\n setImageData({ index: activeImage.index, image: { ...activeImage } });\n setModalState(true);\n };\n\n const onSliceChange = ({ e }) => {\n const tempData = { ...imageData };\n imageData.image[activeTab] = e.target.value;\n setImageData(tempData);\n };\n\n const onChangeSliceNumber = increase => {\n const tempData = { ...imageData };\n imageData.image[activeTab] += increase ? 1 : -1;\n setImageData(tempData);\n };\n\n const onSaveHandle = () => {\n setModalState(false);\n onSaveSlice({ ...imageData });\n };\n\n const currentValue = imageData ? imageData.image[activeTab] : 0;\n\n const minuseButtonDisabled = currentValue === 0;\n const addButtonDisabled =\n (activeTab === 'postSlices' && imageData?.index + currentValue >= imagesCount) ||\n (activeTab === 'preSlices' && imageData?.index - currentValue <= 0);\n\n const getChosenSlices = () => {\n const { prePostSlicesIndexes } = getPrePostSliceItems(\n imageData.index,\n imageData.image.preSlices,\n imageData.image.postSlices,\n );\n const slices = [...prePostSlicesIndexes];\n return slices.join(',');\n };\n\n const hasGeniusShapes = !!activeImage?.geniusAIDataList?.length;\n\n return (\n \n {!playing && (\n
\n Add Pre/Post Slices +\n \n )}\n {imageData && (\n
\n \n
\n
\n
Add Pre/Post Slices \n \n
setModalState(false)} className='btn p-0 modal-close-btn'>\n \n \n
\n
\n Please, indicate how many pre-slices you want to mark for{' '}\n Slice {imageData.index + 1} .\n
\n
\n
\n setActiveTab('preSlices')}\n className={`btn slice-tab ${activeTab === 'preSlices' ? 'active' : ''}`}\n >\n Pre Slice\n \n setActiveTab('postSlices')}\n className={`btn slice-tab ml-3 ${activeTab === 'postSlices' ? 'active' : ''}`}\n >\n Post Slice\n \n
\n
\n
onChangeSliceNumber(false)}\n className='btn btn-slice-change'\n >\n -\n \n
\n \n
\n
onChangeSliceNumber(true)}\n className='btn btn-slice-change'\n >\n +\n \n
\n
\n Chosen Slices: {getChosenSlices()} \n
\n
\n
\n \n Save\n \n
\n
\n \n )}\n
\n );\n};\n\nexport default PrePostSlices;\n","/* eslint-disable no-console */\nimport React, { useState, useEffect, useRef } from 'react';\nimport Slider from 'react-rangeslider';\nimport 'react-rangeslider/umd/rangeslider.min.css';\nimport { IconAiPlay, IconPlayFill, IconSliceNext, IconSlicePrev } from 'shared/components/Icons';\nimport Loading from 'shared/components/Loading';\nimport pause from 'assets/pause.svg';\nimport speedIcon from 'assets/speed.svg';\nimport { getImagesPrePostSlices, getImagesSortedSlices, getPrimarySlices } from './PrePostSlices';\n\nconst stackWords = {\n CC: { top: 'H', bottom: 'F' },\n MLO: { top: 'M', bottom: 'L' },\n};\n\nconst speeds = [\n { name: 'Low', value: 160 },\n { name: 'Medium', value: 80 },\n { name: 'High', value: 40 },\n];\n\nconst modes = [\n { name: 'Genius AI Mode', value: 'genius' },\n { name: 'Normal Mode', value: 'normal' },\n];\n\nconst StackControl = ({\n cornerstoneTools,\n element,\n cornerstone,\n imageType,\n setActiveTool,\n className,\n images,\n play,\n setPlay,\n isGenuine,\n}) => {\n const stackEl = useRef();\n const [speed, setSpeed] = useState(80);\n const [mode, setMode] = useState('normal');\n const [fetch, setFetch] = useState(false);\n const isAIMode = mode === 'genius';\n\n const playIndex = useRef({ index: 0, increase: true, images: undefined });\n const scale = useRef(0);\n const playInterval = useRef();\n\n const primarySlices = isAIMode ? getPrimarySlices(images) : [];\n const allPrePostSlices = isAIMode ? getImagesPrePostSlices(images) : [];\n const allSortedSlices = isAIMode ? getImagesSortedSlices(images) : [];\n const hasNextSlice = primarySlices.indexOf(playIndex.current.index) < primarySlices.length - 1;\n const hasPrevSlice = primarySlices.indexOf(playIndex.current.index) !== 0;\n\n const getStackData = () => {\n const stackData = cornerstoneTools.getToolState(element, 'stack');\n const { currentImageIdIndex, imageIds } = stackData.data[0];\n return { currentImageIdIndex, imageIds };\n };\n\n const { imageIds } = getStackData();\n\n const changeHandle = index => {\n if (fetch || play) return;\n if (!playIndex.current.images) {\n loadAllImages();\n return;\n }\n playIndex.current.index = index;\n scale.current = index;\n changeStackImage(playIndex.current.images, index);\n };\n\n const changeStackImage = (images, index) => {\n if (images[index] !== undefined) {\n const viewport = cornerstone.getViewport(element);\n const stackData = cornerstoneTools.getToolState(element, 'stack');\n stackData.data[0].currentImageIdIndex = Number(index);\n cornerstone.displayImage(element, images[index], viewport);\n }\n };\n\n const playStack = images => {\n try {\n let { index, increase } = playIndex.current;\n index += increase ? 1 : -1;\n if (!imageIds[index]) {\n increase = !increase;\n index += increase ? 2 : -2;\n }\n changeStackImage(images, index);\n playIndex.current.index = index;\n playIndex.current.increase = increase;\n } catch (err) {\n console.log(err);\n }\n };\n\n const playStackSlices = images => {\n try {\n let { index, increase } = playIndex.current;\n const activeSlice = allSortedSlices.findIndex(i => i.includes(index));\n const nearPrimary = activeSlice !== -1 ? allSortedSlices[activeSlice] : allSortedSlices[0];\n const sliceImages = [...(nearPrimary || [])].sort((a, b) => a - b);\n const currentIndex = sliceImages.indexOf(index);\n index = sliceImages[currentIndex === -1 ? 0 : currentIndex + (increase ? 1 : -1)];\n if (!imageIds[index]) {\n increase = !increase;\n index = sliceImages[currentIndex === -1 ? 0 : currentIndex + (increase ? 1 : -1)];\n }\n changeStackImage(images, index);\n playIndex.current.index = index;\n playIndex.current.increase = increase;\n } catch (err) {\n console.log(err);\n }\n };\n\n const setPlayInterval = (speed, changedToAIMode) => {\n if (changedToAIMode || isAIMode) {\n playInterval.current = setInterval(playStackSlices, speed, playIndex.current.images);\n } else {\n playInterval.current = setInterval(playStack, speed, playIndex.current.images);\n }\n };\n\n const loadAllImages = async play => {\n setFetch(true);\n if (!playIndex.current.images) {\n const { imageIds } = getStackData();\n const imageLoads = imageIds.map(async item => await cornerstone.loadAndCacheImage(item));\n const images = await Promise.all(imageLoads);\n playIndex.current.images = images;\n }\n setFetch(false);\n if (play) setPlayInterval(speed);\n };\n\n const onElementScroll = e => {\n e.preventDefault();\n\n if (!playIndex.current.images) {\n if (fetch || play) return;\n loadAllImages();\n return;\n }\n\n const { imageIds } = getStackData();\n scale.current += e.deltaY * -0.01;\n scale.current = Math.min(Math.max(0, scale.current), imageIds.length);\n\n if (playIndex.current.index !== Math.floor(scale.current)) {\n changeHandle(Math.floor(scale.current));\n }\n };\n\n const onChangeStart = () => {\n setActiveTool({ type: 'remove' });\n };\n\n const handleUserKeyPress = event => {\n const { keyCode } = event;\n if (keyCode === 32)\n setPlay(p => {\n return !p;\n });\n };\n\n const stopPlayerUnmount = () => {\n window.removeEventListener('keydown', handleUserKeyPress);\n setPlay(false);\n clearInterval(playInterval.current);\n playIndex.current = { index: 0, increase: true, images: undefined };\n };\n\n const onSpeedChange = async value => {\n setSpeed(value);\n if (play) {\n clearInterval(playInterval.current);\n setPlayInterval(value);\n }\n };\n\n const onPlayerModeChanges = value => {\n setMode(value);\n if (play) {\n clearInterval(playInterval.current);\n setPlayInterval(speed, value === 'genius');\n }\n };\n\n const renderLines = length => {\n const lines = [];\n for (let index = length - 1; index >= 0; index--) {\n const hasGenuie = images[index]?.geniusAIDataList?.length > 0;\n const hasPrimarySlice =\n hasGenuie || (hasGenuie && (!!images[index]?.postSlices || images[index].preSlices));\n const hasPrePostSlice = !hasPrimarySlice && allPrePostSlices.includes(index);\n lines.push(\n ,\n );\n }\n return lines;\n };\n\n const onMovePrimarySlice = isNext => {\n setPlay(false);\n const currentIndex = primarySlices.indexOf(playIndex.current.index);\n const newIndex = currentIndex === -1 ? 0 : currentIndex + (isNext ? 1 : -1);\n changeHandle(primarySlices[newIndex]);\n };\n\n useEffect(() => {\n playIndex.current = { index: 0, increase: true, images: undefined };\n scale.current = 0;\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n if (play) {\n element.onwheel = null;\n loadAllImages(true);\n } else {\n scale.current = playIndex.current.index;\n element.onwheel = onElementScroll;\n clearInterval(playInterval.current);\n }\n //eslint-disable-next-line\n }, [play]);\n\n useEffect(() => {\n window.addEventListener('keydown', handleUserKeyPress);\n return () => {\n stopPlayerUnmount();\n };\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n setTimeout(() => {\n images.map(item => cornerstone.loadAndCacheImage(item.url));\n }, 100);\n stopPlayerUnmount();\n //eslint-disable-next-line\n }, []);\n\n return (\n <>\n \n
\n {renderLines(imageIds.length)}\n
\n
\n
\n
\n {speeds.map(item => (\n onSpeedChange(item.value)}\n >\n {item.name}\n \n ))}\n
\n
\n
\n
{\n setPlay(p => !p);\n e.preventDefault();\n e.stopPropagation();\n }}\n >\n {fetch ? (\n \n ) : play ? (\n \n ) : (\n \n )}\n \n {!play && isGenuine && (\n
\n {modes.map(item => (\n onPlayerModeChanges(item.value)}\n >\n {item.name}\n \n ))}\n
\n )}\n
\n
{stackWords[imageType].top}
\n
\n
{stackWords[imageType].bottom}
\n
\n {Number(getStackData().currentImageIdIndex) + 1}/{imageIds.length}\n
\n {isAIMode && (\n
\n \n \n \n \n \n \n
\n )}\n
Buffering in Process
\n
\n {play && isAIMode && (\n \n Genius AI Mode \n
\n )}\n >\n );\n};\n\nexport default StackControl;\n","/**\n * Triggers a CustomEvent.\n * @public\n * @method triggerEvent\n *\n * @param {EventTarget} el The element or EventTarget to trigger the event upon.\n * @param {String} type The event type name.\n * @param {Object|null} [detail=null] The event data to be sent.\n * @returns {Boolean} The return value is false if at least one event listener called preventDefault(). Otherwise it returns true.\n */\nexport default function triggerEvent(el, type, detail = null) {\n let event;\n\n // This check is needed to polyfill CustomEvent on IE11-\n if (typeof window.CustomEvent === 'function') {\n event = new CustomEvent(type, {\n detail,\n cancelable: true,\n });\n } else {\n event = document.createEvent('CustomEvent');\n event.initCustomEvent(type, true, true, detail);\n }\n\n return el.dispatchEvent(event);\n}\n","/* eslint-disable consistent-return */\nimport * as cornerstone from 'cornerstone-core';\nimport uuidv4 from './../util/uuidv4.js';\nimport triggerEvent from './../util/triggerEvent.js';\n\n/**\n * Implements an imageId specific tool state management strategy. This means that\n * Measurements data is tied to a specific imageId and only visible for enabled elements\n * That are displaying that imageId.\n * @public\n * @constructor newImageIdSpecificToolStateManager\n * @memberof StateManagement\n *\n * @returns {Object} An imageIdSpecificToolStateManager instance.\n */\nfunction newImageIdSpecificToolStateManager() {\n let toolState = {};\n\n // Here we add tool state, this is done by tools as well\n // As modules that restore saved state\n\n function saveImageIdToolState(imageId) {\n return toolState[imageId];\n }\n\n function restoreImageIdToolState(imageId, imageIdToolState) {\n toolState[imageId] = imageIdToolState;\n }\n\n function saveToolState() {\n return toolState;\n }\n\n function restoreToolState(savedToolState) {\n toolState = savedToolState;\n }\n\n // Here we add tool state, this is done by tools as well\n // As modules that restore saved state\n function addElementToolState(element, toolType, data) {\n const enabledElement = cornerstone.getEnabledElement(element);\n\n // If we don't have an image for this element exit early\n if (!enabledElement.image) {\n return;\n }\n addImageIdToolState(enabledElement.image.imageId, toolType, data);\n }\n\n function addImageIdToolState(imageId, toolType, data) {\n // If we don't have any tool state for this imageId, add an empty object\n if (toolState.hasOwnProperty(imageId) === false) {\n toolState[imageId] = {};\n }\n\n const imageIdToolState = toolState[imageId];\n\n // If we don't have tool state for this type of tool, add an empty object\n if (imageIdToolState.hasOwnProperty(toolType) === false) {\n imageIdToolState[toolType] = {\n data: [],\n };\n }\n\n const toolData = imageIdToolState[toolType];\n\n // Finally, add this new tool to the state\n toolData.data.push(data);\n }\n\n function getElementToolState(element, toolType) {\n const enabledElement = cornerstone.getEnabledElement(element);\n\n // If the element does not have an image return undefined.\n if (!enabledElement.image) {\n return;\n }\n\n return getImageIdToolState(enabledElement.image.imageId, toolType);\n }\n\n // Here you can get state - used by tools as well as modules\n // That save state persistently\n function getImageIdToolState(imageId, toolType) {\n // If we don't have any tool state for this imageId, return undefined\n if (toolState.hasOwnProperty(imageId) === false) {\n return;\n }\n\n const imageIdToolState = toolState[imageId];\n\n // If we don't have tool state for this type of tool, return undefined\n if (imageIdToolState.hasOwnProperty(toolType) === false) {\n return;\n }\n\n return imageIdToolState[toolType];\n }\n\n // Clears all tool data from this toolStateManager.\n function clearElementToolState(element) {\n const enabledElement = cornerstone.getEnabledElement(element);\n\n if (!enabledElement.image) {\n return;\n }\n clearImageIdToolState(enabledElement.image.imageId);\n }\n\n function clearImageIdToolState(imageId) {\n if (toolState.hasOwnProperty(imageId) === false) {\n return;\n }\n\n delete toolState[imageId];\n }\n\n return {\n get: getElementToolState,\n add: addElementToolState,\n clear: clearElementToolState,\n getImageIdToolState,\n addImageIdToolState,\n clearImageIdToolState,\n saveImageIdToolState,\n restoreImageIdToolState,\n saveToolState,\n restoreToolState,\n toolState,\n };\n}\n\n// A global imageIdSpecificToolStateManager - the most common case is to share state between all\n// Visible enabled images\nconst globalImageIdSpecificToolStateManager = newImageIdSpecificToolStateManager();\n\n/**\n * Returns the toolstate for a specific element.\n * @public\n * @function getElementToolStateManager\n *\n * @param {HTMLElement} element The element.\n * @returns {Object} The toolState.\n */\nfunction getElementToolStateManager(element) {\n const enabledElement = cornerstone.getEnabledElement(element);\n // If the enabledElement has no toolStateManager, create a default one for it\n // NOTE: This makes state management element specific\n\n if (enabledElement.toolStateManager === undefined) {\n enabledElement.toolStateManager = globalImageIdSpecificToolStateManager;\n }\n\n return enabledElement.toolStateManager;\n}\n\n/**\n * Adds tool state to the toolStateManager, this is done by tools as well\n * as modules that restore saved state.\n * @public\n * @method addToolState\n *\n * @param {HTMLElement} element The element.\n * @param {string} toolType The toolType of the state.\n * @param {Object} measurementData The data to store in the state.\n * @returns {undefined}\n */\nfunction addToolState(element, toolType, measurementData) {\n const toolStateManager = getElementToolStateManager(element);\n\n measurementData.uuid = measurementData.uuid || uuidv4();\n toolStateManager.add(element, toolType, measurementData);\n\n const eventType = 'cornerstonetoolsmeasurementremoved';\n const eventData = {\n toolType,\n element,\n measurementData,\n };\n\n triggerEvent(element, eventType, eventData);\n}\n\n/**\n * Returns tool specific state of an element. Used by tools as well as modules\n * that save state persistently\n * @export\n * @public\n * @method\n * @name getToolState\n *\n * @param {HTMLElement} element The element.\n * @param {string} toolType The toolType of the state.\n * @returns {Object} The element's state for the given toolType.\n */\nfunction getToolState(element, toolType) {\n const toolStateManager = getElementToolStateManager(element);\n\n return toolStateManager.get(element, toolType);\n}\n\n/**\n * Removes specific tool state from the toolStateManager.\n * @public\n * @method removeToolState\n *\n * @param {HTMLElement} element The element.\n * @param {string} toolType The toolType of the state.\n * @param {Object} data The data to remove from the toolStateManager.\n * @returns {undefined}\n */\nfunction removeToolState(element, toolType, data) {\n const toolStateManager = getElementToolStateManager(element);\n const toolData = toolStateManager.get(element, toolType);\n\n if (!toolData || !toolData.data || !toolData.data.length) {\n return;\n }\n\n // Find this tool data\n let indexOfData = -1;\n\n for (let i = 0; i < toolData.data.length; i++) {\n if (toolData.data[i] === data) {\n indexOfData = i;\n }\n }\n\n if (indexOfData !== -1) {\n toolData.data.splice(indexOfData, 1);\n\n const eventType = 'cornerstonetoolsmeasurementremoved';\n const eventData = {\n toolType,\n element,\n measurementData: data,\n };\n\n triggerEvent(element, eventType, eventData);\n }\n}\n\nexport { getToolState, addToolState, removeToolState };\n","/* eslint-disable */\nexport default function uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n let r = (Math.random() * 16) | 0,\n v = c == 'x' ? r : (r & 0x3) | 0x8;\n\n return v.toString(16);\n });\n}\n","let defaultColor = 'white';\nlet activeColor = 'greenyellow';\nlet fillColor = 'transparent';\n\nfunction setFillColor(color) {\n fillColor = color;\n}\n\nfunction getFillColor() {\n return fillColor;\n}\n\nfunction setToolColor(color) {\n defaultColor = color;\n}\n\nfunction getToolColor() {\n return defaultColor;\n}\n\nfunction setActiveColor(color) {\n activeColor = color;\n}\n\nfunction getActiveColor() {\n return activeColor;\n}\n\nfunction getColorIfActive(data) {\n if (data.color) {\n return data.color;\n }\n\n return data.active ? activeColor : defaultColor;\n}\n\nconst toolColors = {\n setFillColor,\n getFillColor,\n setToolColor,\n getToolColor,\n setActiveColor,\n getActiveColor,\n getColorIfActive,\n};\n\nexport default toolColors;\n","/* eslint-disable import/no-anonymous-default-export */\n/**\n * This function manages the {@link https://www.w3.org/TR/2dcontext/#the-canvas-state|save/restore}\n * pattern for working in a new context state stack. The parameter `fn` is passed the `context` and can\n * execute any API calls in a clean stack.\n * @public\n * @method draw\n * @memberof Drawing\n *\n * @param {CanvasRenderingContext2D} context - Target Canvas\n * @param {ContextFn} fn - A function which performs drawing operations within the given context.\n * @returns {undefined}\n */\nexport default function(context, fn) {\n context.save();\n fn(context);\n context.restore();\n}\n","let defaultWidth = 1;\nlet activeWidth = 2;\n\nfunction setToolWidth(width) {\n defaultWidth = width;\n}\n\nfunction getToolWidth() {\n return defaultWidth;\n}\n\nfunction setActiveWidth(width) {\n activeWidth = width;\n}\n\nfunction getActiveWidth() {\n return activeWidth;\n}\n\nconst toolStyle = {\n setToolWidth,\n getToolWidth,\n setActiveWidth,\n getActiveWidth,\n};\n\nexport default toolStyle;\n","/* eslint-disable import/no-anonymous-default-export */\nimport toolStyle from './../stateManagement/toolStyle.js';\n\n/**\n * This function manages the beginPath/stroke pattern for working with\n * {@link https://www.w3.org/TR/2dcontext/#drawing-paths-to-the-canvas|path objects}.\n *\n * @public\n * @function path\n * @memberof Drawing\n *\n * @param {CanvasRenderingContext2D} context - Context to add path to\n * @param {Object} [options={}] - Drawing Options\n * @param {StrokeStyle} [options.color] - The stroke style of the path.\n * @param {number} [options.lineWidth] - The width of lines in the path. If null, no line width is set.\n * If undefined then toolStyle.getToolWidth() is set.\n * @param {FillStyle} [options.fillStyle] - The style to fill the path with. If undefined then no filling is done.\n * @param {Number[]} [options.lineDash] - The {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash|dash pattern}\n * to use on the lines.\n * @param {boolean} [options.shouldDrawLines = true] Whether context.stroke should be evoked.\n * @param {ContextFn} fn - A drawing function to execute with the provided stroke pattern.\n * @returns {undefined}\n */\nexport default function(context, options = {}, fn) {\n const { color, lineWidth, fillStyle, lineDash, shouldDrawLines = true } = options;\n\n context.beginPath();\n context.strokeStyle = color || context.strokeStyle;\n\n context.lineWidth =\n lineWidth || (lineWidth === undefined && toolStyle.getToolWidth()) || context.lineWidth;\n if (lineDash) {\n context.setLineDash(lineDash);\n }\n\n fn(context);\n\n if (fillStyle) {\n context.fillStyle = fillStyle;\n context.fill();\n }\n\n if (shouldDrawLines) {\n context.stroke();\n }\n\n if (lineDash) {\n context.setLineDash([]);\n }\n}\n","import * as cornerstone from 'cornerstone-core';\nimport toolStyle from './../stateManagement/toolStyle.js';\nimport toolColors from './../stateManagement/toolColors.js';\nimport path from './path.js';\n\nconst drawArrow = (c, { x, y }) => {\n c.setTransform(1, 0, 0, 1, 10, 0);\n c.translate(x - 8, y + 4);\n c.arrow(30, 20, 0, 0, [0, 2, -10, 2, -10, 5]);\n};\n\nconst drawFlake = (c, { x, y }, percent) => {\n x -= 12;\n y -= 12;\n\n if (percent) {\n c.font = '16px Roboto';\n c.fillStyle = '#EDD500';\n c.fillText(`${percent}%`, x, y - 5);\n }\n\n c.setTransform(1.33333, 0, 0, 1.33333, x, y);\n c.setTransform(1, 0, 0, 1, x, y);\n c.setTransform(1, 0, 0, 1, x + 8.5, y + 8.475);\n c.setTransform(1, 0, 0, 1, x + 10.025, y);\n c.beginPath();\n c.moveTo(0, 0);\n c.lineTo(4, 0);\n c.lineTo(4, 24);\n c.lineTo(0, 24);\n c.closePath();\n c.fillStyle = '#EDD500';\n c.fill('nonzero');\n c.setTransform(1, 0, 0, 1, x + 8.5, y + 6.719);\n c.beginPath();\n c.moveTo(13.57, -2.5);\n c.lineTo(15.551, 1.108);\n c.lineTo(-6.519, 14.477);\n c.lineTo(-8.5, 10.869);\n c.lineTo(13.57, -2.5);\n c.closePath();\n c.fill('nonzero');\n c.beginPath();\n c.moveTo(-6.519, -2.5);\n c.lineTo(-8.5, 1.108);\n c.lineTo(13.57, 14.408);\n c.lineTo(15.551, 10.8);\n c.lineTo(-6.519, -2.5);\n c.closePath();\n c.fill('nonzero');\n};\n\nconst drawCross = (c, { x, y }, percent) => {\n x -= 12;\n y -= 12;\n\n if (percent) {\n c.font = '16px Roboto';\n c.fillStyle = '#EDD500';\n c.fillText(`${percent}%`, x, y - 5);\n }\n\n c.setTransform(1.33333, 0, 0, 1.33333, x, y);\n c.setTransform(1, 0, 0, 1, x, y);\n c.setTransform(1, 0, 0, 1, x + 5.9, y + 6);\n c.setTransform(1, 0, 0, 1, x + 8.159, y);\n c.beginPath();\n c.moveTo(0, 12);\n c.bezierCurveTo(0, 5.37258, 1.71967, 0, 3.841, 0);\n c.bezierCurveTo(5.96233, 0, 7.682, 5.37258, 7.682, 12);\n c.bezierCurveTo(7.682, 18.62742, 5.96233, 24, 3.841, 24);\n c.bezierCurveTo(1.71967, 24, 0, 18.62742, 0, 12);\n c.fillStyle = '#EDD500';\n c.fill('nonzero');\n c.setTransform(1, 0, 0, 1, x + 5.9, y + 2.427);\n c.beginPath();\n c.moveTo(18.1, 10.141);\n c.bezierCurveTo(14.52911, 12.50002, 10.37702, 13.82904, 6.1, 13.982);\n c.bezierCurveTo(1.82298, 13.82904, -2.3291, 12.50002, -5.9, 10.141);\n c.bezierCurveTo(-2.32911, 7.78198, 1.82299, 6.45296, 6.1, 6.3);\n c.bezierCurveTo(10.37702, 6.45296, 14.5291, 7.78198, 18.1, 10.141);\n // c.closePath();\n c.fill('nonzero');\n};\n\nconst drawTriangle = (c, { x, y }, percent) => {\n if (percent) {\n c.font = '16px Roboto';\n c.fillStyle = '#EDD500';\n c.fillText(`${percent}%`, x - 10, y - 20);\n }\n\n const side = 26;\n const h = side * (Math.sqrt(3) / 2);\n\n c.fillStyle = '#EDD500';\n\n // c.save();\n c.translate(x, y);\n\n // c.beginPath();\n\n c.moveTo(0, -h / 2);\n c.lineTo(-side / 2, h / 2);\n c.lineTo(side / 2, h / 2);\n c.lineTo(0, -h / 2);\n c.stroke();\n c.fill();\n};\n\nconst shapeMaps = {\n arrow: drawArrow,\n 3: drawFlake,\n 1: drawCross,\n 2: drawTriangle,\n};\n\nconst drawHandles = (context, evtDetail, handles, options = {}) => {\n const element = evtDetail.element;\n const defaultColor = toolColors.getToolColor();\n\n context.strokeStyle = options.color || defaultColor;\n\n const handleKeys = Object.keys(handles);\n\n for (let i = 0; i < handleKeys.length; i++) {\n const handleKey = handleKeys[i];\n const handle = handles[handleKey];\n\n if (handle.drawnIndependently === true) {\n continue;\n }\n\n if (options.drawHandlesIfActive === true && !handle.active) {\n continue;\n }\n\n const lineWidth = handle.active ? toolStyle.getActiveWidth() : toolStyle.getToolWidth();\n const fillStyle = options.fill;\n const shape = handle.shape || 'arrow';\n\n path(\n context,\n {\n lineWidth,\n fillStyle,\n },\n context => {\n const handleCanvasCoords = cornerstone.pixelToCanvas(element, handle);\n shapeMaps[shape](context, handleCanvasCoords, handle.percentage);\n },\n );\n }\n};\n\nexport default drawHandles;\n","/* eslint-disable import/no-anonymous-default-export */\n/**\n * Create a new {@link CanvasRenderingContext2D|context} object for the given {@link HTMLCanvasElement|canvas}\n * and set the transform to the {@link https://www.w3.org/TR/2dcontext/#transformations|identity transform}.\n *\n * @public\n * @function getNewContext\n * @memberof Drawing\n *\n * @param {HTMLCanvasElement} canvas - Canvas you would like the context for\n * @returns {CanvasRenderingContext2D} - The provided canvas's 2d context\n */\nexport default function(canvas) {\n const context = canvas.getContext('2d');\n\n context.setTransform(1, 0, 0, 1, 0, 0);\n\n return context;\n}\n","import toolColors from './../stateManagement/toolColors.js';\n\nconst configuration = {\n iconSize: 16,\n viewBox: {\n x: 16,\n y: 16,\n },\n mousePoint: {\n x: 8,\n y: 8,\n },\n mousePointerGroupString: `\n \n \n `,\n};\n\n/* eslint-disable valid-jsdoc */\n\n/*\nMACROS - The following keys will have the appropriate value injected when\nan SVG is requested:\n\n- ACTIVE_COLOR => options.activeColor || toolColors.getActiveColor();\n- TOOL_COLOR => options.toolColor || toolColors.getToolColor();\n- FILL_COLOR => options.fillColor || toolColors.getFillColor();\n*/\n\nclass MouseCursor {\n constructor(iconGroupString, options) {\n this.iconGroupString = iconGroupString;\n this.options = Object.assign({}, configuration, options);\n }\n\n /**\n * Returns an SVG of the icon only.\n *\n * @param {Object} options - An object which overrides default properties of the returned SVG.\n * @returns {Blob} The SVG of the icon.\n */\n getIconSVG(options = {}) {\n const svgString = this._generateIconSVGString(options);\n\n return new Blob([svgString], { type: 'image/svg+xml' });\n }\n\n /**\n * Returns a string representation of the SVG of the icon only.\n *\n * @param {Object} options - An object which overrides default properties of the returned SVG.\n * @returns {string} The stringified SVG of the icon.\n */\n getIconSVGString(options = {}) {\n return this._generateIconSVGString(options);\n }\n\n /**\n * Returns an SVG of the icon + pointer.\n *\n * @param {Object} options - An object which overrides default properties of the returned SVG.\n * @returns {Blob} The SVG of the icon + pointer..\n */\n getIconWithPointerSVG(options = {}) {\n const svgString = this._generateIconWithPointerSVGString(options);\n\n return new Blob([svgString], { type: 'image/svg+xml' });\n }\n\n /**\n * Returns a string representation of the SVG of the icon + pointer.\n *\n * @param {Object} options - An object which overrides default properties of the returned SVG.\n * @returns {string} The stringified SVG of the icon + pointer.\n */\n getIconWithPointerString(options = {}) {\n return this._generateIconWithPointerSVGString(options);\n }\n\n /**\n * Returns the mousePoint as a space seperated string.\n *\n * @returns {string} The mousePoint.\n */\n get mousePoint() {\n const mousePoint = this.options.mousePoint;\n\n return `${mousePoint.x} ${mousePoint.y}`;\n }\n\n /**\n * Generates a string representation of the icon + pointer.\n *\n * @param {Object} options - An object which overrides default properties of the returned string.\n * @returns {string} The SVG as a string.\n */\n _generateIconWithPointerSVGString(options = {}) {\n const svgOptions = Object.assign({}, this.options, options);\n const { mousePointerGroupString, iconSize, viewBox } = svgOptions;\n\n const scale = iconSize / Math.max(viewBox.x, viewBox.y);\n const svgSize = 16 + iconSize;\n\n const svgString = `\n \n \n ${mousePointerGroupString}\n \n \n ${this.iconGroupString}\n \n `;\n\n return this._injectColors(svgString, svgOptions);\n }\n\n /**\n * Generates a string representation of the icon.\n *\n * @param {Object} options - An object which overrides default properties of the returned string.\n * @returns {string} The SVG as a string.\n */\n _generateIconSVGString(options = {}) {\n const svgOptions = Object.assign({}, this.options, options);\n const { iconSize, viewBox } = svgOptions;\n\n const svgString = `\n \n ${this.iconGroupString}\n `;\n\n return this._injectColors(svgString, svgOptions);\n }\n\n /**\n * Replaces ACTIVE_COLOR, TOOL_COLOR and FILL_COLOR in svgString with their appropriate values.\n *\n * @param {string} svgString - The string to modify.\n * @param {Object} options - Optional overrides for the colors.\n * @returns {string} The string with color values injected.\n */\n _injectColors(svgString, options = {}) {\n const activeColor = options.activeColor || toolColors.getActiveColor();\n const toolColor = options.toolColor || toolColors.getToolColor();\n const fillColor = options.fillColor || toolColors.getFillColor();\n\n return svgString\n .replace(/ACTIVE_COLOR/g, `${activeColor}`)\n .replace(/TOOL_COLOR/g, `${toolColor}`)\n .replace(/FILL_COLOR/g, `${fillColor}`);\n }\n}\n\nexport const probeCursor = new MouseCursor(\n ` `,\n {\n viewBox: {\n x: 1792,\n y: 1792,\n },\n },\n);\n","import csTools from 'cornerstone-tools';\nimport * as cornerstone from 'cornerstone-core';\nimport cornerstoneMath from 'cornerstone-math';\n// State\nimport { getToolState, addToolState, removeToolState } from './stateManagement/toolState.js';\nimport toolColors from './stateManagement/toolColors.js';\n// Drawing\nimport { getNewContext, draw } from './drawing/index.js';\nimport drawHandles from './drawing/drawHandles.js';\n// Utilities\nimport { probeCursor } from './cursors/index.js';\n\nconst BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool');\n\nexport default class ArrowTool extends BaseAnnotationTool {\n constructor(props = {}) {\n const defaultProps = {\n name: 'ArrowTool',\n supportedInteractionTypes: ['Mouse', 'Touch'],\n svgCursor: probeCursor,\n configuration: {\n drawHandles: true,\n },\n };\n\n super(props, defaultProps);\n this.touchPressCallback = this._onClickArrow.bind(this);\n this.doubleClickCallback = this._onClickArrow.bind(this);\n }\n\n createEventData({ x, y }) {\n return {\n visible: true,\n active: true,\n color: undefined,\n invalidated: true,\n handles: {\n end: {\n x,\n y,\n highlight: true,\n active: true,\n },\n },\n };\n }\n\n createNewMeasurement(eventData) {\n const { onArrowAdd } = this.initialConfiguration;\n const goodEventData = eventData && eventData.currentPoints && eventData.currentPoints.image;\n if (!goodEventData) {\n return {};\n }\n\n if (onArrowAdd) onArrowAdd(eventData.image, eventData);\n return this.createEventData(eventData.currentPoints.image);\n }\n\n pointNearTool(element, data, coords) {\n const hasEndHandle = data && data.handles && data.handles.end;\n const validParameters = hasEndHandle;\n if (!validParameters || data.visible === false) {\n return false;\n }\n const probeCoords = cornerstone.pixelToCanvas(element, data.handles.end);\n return cornerstoneMath.point.distance(probeCoords, coords) < 30;\n }\n\n updateCachedStats(image, element, data) {\n const { onArrowMove } = this.initialConfiguration;\n clearTimeout(this.updateEvent);\n\n this.updateEvent = setTimeout(() => {\n if (onArrowMove) onArrowMove(image, data);\n }, 500);\n }\n\n createInitialData(handles) {\n const data = [];\n handles.forEach(item => {\n data.push(this.createEventData(item));\n });\n return this.createEventData(handles[0]);\n }\n\n addedToolInitialData(target) {\n const { data } = this.initialConfiguration;\n if (this.addedInitialData || !data || !data.length) return;\n data.forEach(item => {\n addToolState(target, this.name, this.createEventData(item));\n });\n this.addedInitialData = true;\n }\n\n renderToolData(evt) {\n this.addedToolInitialData(evt.currentTarget);\n const eventData = evt.detail;\n const { handleRadius } = this.configuration;\n const toolData = getToolState(evt.currentTarget, this.name);\n\n if (!toolData) {\n return;\n }\n\n const context = getNewContext(eventData.canvasContext.canvas);\n\n for (let i = 0; i < toolData.data.length; i++) {\n const data = toolData.data[i];\n\n if (data.visible === false) {\n continue;\n }\n\n draw(context, context => {\n const color = toolColors.getColorIfActive(data);\n if (this.configuration.drawHandles) {\n drawHandles(context, eventData, data.handles, {\n handleRadius,\n color,\n });\n }\n });\n }\n }\n\n _onClickArrow(evt) {\n if (!window.confirm('Remove Arrow ?')) return;\n const { onArrowRemove } = this.initialConfiguration;\n const eventData = evt.detail;\n const { element, currentPoints } = eventData;\n\n const coords = currentPoints.canvas;\n const toolData = getToolState(element, this.name);\n\n // Now check to see if there is a handle we can move\n if (!toolData) {\n return;\n }\n\n for (let i = 0; i < toolData.data.length; i++) {\n const data = toolData.data[i];\n if (this.pointNearTool(element, data, coords)) {\n removeToolState(element, this.name, data);\n if (onArrowRemove) onArrowRemove(eventData.image, data);\n cornerstone.updateImage(element);\n\n evt.stopImmediatePropagation();\n evt.preventDefault();\n evt.stopPropagation();\n\n return;\n }\n }\n }\n}\n","import csTools from 'cornerstone-tools';\nimport * as cornerstone from 'cornerstone-core';\nimport cornerstoneMath from 'cornerstone-math';\n// State\nimport { getToolState, addToolState, removeToolState } from './stateManagement/toolState.js';\n// Drawing\nimport { getNewContext, draw } from './drawing/index.js';\nimport drawHandles from './drawing/drawHandles.js';\n// Utilities\nimport { probeCursor } from './cursors/index.js';\n\nconst BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool');\n\nexport default class GenuineTool extends BaseAnnotationTool {\n constructor(props = {}) {\n const defaultProps = {\n name: 'GenuineTool',\n supportedInteractionTypes: ['Mouse', 'Touch'],\n svgCursor: probeCursor,\n configuration: {\n drawHandles: true,\n },\n };\n\n super(props, defaultProps);\n this.touchPressCallback = this._onDBClickPoint.bind(this);\n this.doubleClickCallback = this._onDBClickPoint.bind(this);\n }\n\n activeCallback(element, data) {\n this.shape = data.shape;\n }\n\n disabledCallback(element) {}\n\n createEventData({ x, y, shape, percentage }) {\n return {\n visible: true,\n active: true,\n color: undefined,\n invalidated: true,\n handles: {\n end: {\n percentage: percentage,\n shape: shape || this.shape,\n x,\n y,\n highlight: true,\n active: true,\n },\n },\n };\n }\n\n createNewMeasurement(eventData) {\n // const { onArrowAdd } = this.initialConfiguration;\n const goodEventData = eventData && eventData.currentPoints && eventData.currentPoints.image;\n if (!goodEventData) {\n return {};\n }\n\n const currentDataLength = getToolState(eventData.element, this.name)?.data.length;\n\n setTimeout(() => {\n const toolData = getToolState(eventData.element, this.name);\n if (!toolData || toolData.data?.length === currentDataLength) return;\n const percentage = prompt('Probability (Enter %):');\n this._onUpdatePoint(eventData, percentage, !percentage);\n }, 400);\n\n // if (onArrowAdd) onArrowAdd(eventData.image, eventData);\n return this.createEventData({ ...eventData.currentPoints.image, percentage: null });\n }\n\n pointNearTool(element, data, coords) {\n const hasEndHandle = data && data.handles && data.handles.end;\n const validParameters = hasEndHandle;\n if (!validParameters || data.visible === false) {\n return false;\n }\n const probeCoords = cornerstone.pixelToCanvas(element, data.handles.end);\n return cornerstoneMath.point.distance(probeCoords, coords) < 30;\n }\n\n updateCachedStats(image, element, data) {\n const { onArrowMove } = this.initialConfiguration;\n clearTimeout(this.updateEvent);\n\n this.updateEvent = setTimeout(() => {\n if (onArrowMove) onArrowMove(image, data);\n }, 500);\n }\n\n createInitialData(handles) {\n const data = [];\n handles.forEach(item => {\n data.push(this.createEventData(item));\n });\n return this.createEventData(handles[0]);\n }\n\n addedToolInitialData(target) {\n const { data } = this.initialConfiguration;\n if (this.addedInitialData || !data || !data.length) return;\n data.forEach(item => {\n addToolState(target, this.name, this.createEventData(item));\n });\n this.addedInitialData = true;\n }\n\n renderToolData(evt) {\n this.addedToolInitialData(evt.currentTarget);\n const eventData = evt.detail;\n const { handleRadius } = this.configuration;\n const toolData = getToolState(evt.currentTarget, this.name);\n\n if (!toolData) {\n return;\n }\n\n const context = getNewContext(eventData.canvasContext.canvas);\n\n for (let i = 0; i < toolData.data.length; i++) {\n const data = toolData.data[i];\n\n if (data.visible === false) {\n continue;\n }\n\n draw(context, context => {\n // const color = toolColors.getColorIfActive(data);\n if (this.configuration.drawHandles) {\n drawHandles(context, eventData, data.handles, {\n handleRadius,\n color: '#EDD500',\n });\n }\n });\n }\n }\n\n _onDBClickPoint(evt) {\n const percentage = prompt('Update Probability (Enter %):');\n const eventData = evt.detail;\n\n this._onUpdatePoint(eventData, percentage, !percentage);\n\n evt.stopImmediatePropagation();\n evt.preventDefault();\n evt.stopPropagation();\n\n return;\n }\n\n _onUpdatePoint(eventData, percentage, isRemove) {\n const { onArrowMove, onArrowRemove } = this.initialConfiguration;\n const { element, currentPoints } = eventData;\n\n const coords = currentPoints.canvas;\n const toolData = getToolState(element, this.name);\n\n // Now check to see if there is a handle we can move\n if (!toolData) {\n return;\n }\n\n for (let i = 0; i < toolData.data.length; i++) {\n const data = toolData.data[i];\n if (this.pointNearTool(element, data, coords)) {\n if (isRemove) {\n removeToolState(element, this.name, data);\n if (onArrowRemove) onArrowRemove(eventData.image, data);\n } else {\n data.handles.end.percentage = percentage;\n if (onArrowMove) onArrowMove(eventData.image, data);\n }\n\n cornerstone.updateImage(element);\n\n return;\n }\n }\n }\n}\n","/* eslint-disable no-console */\nimport React, { useRef, useEffect, useState } from 'react';\nimport {\n zoomImageMouseClick,\n maximazeElement,\n getFindingsCoordinates,\n createFindingEventData,\n getDicomImagesObj,\n createGenuieEventData,\n sumShapes,\n} from '../utils';\nimport Thumbnail from './Thumbnail';\n// import ScaleRules from './ScaleRules';\nimport StackControl from './StackControl';\n\nimport * as cornerstone from 'cornerstone-core';\nimport cornerstoneMath from 'cornerstone-math';\nimport cornerstoneTools from 'cornerstone-tools/dist/cornerstoneTools';\nimport * as cornerstoneWebImageLoader from 'cornerstone-web-image-loader';\nimport Hammer from 'hammerjs';\nimport Loading from 'shared/components/Loading';\nimport ArrowTool from '../plugins/ArrowTool';\nimport GenuineTool from '../plugins/GenuineTool';\n\nimport iconMax from 'assets/arrows/2p.svg';\nimport { genuieShapes, getDefaultType, getToolByType } from '../configs';\nimport { useDispatch } from 'react-redux';\nimport { getToolState } from '../plugins/stateManagement/toolState';\nimport { IconGenuine } from 'shared/components/Icons';\nimport { setCanBeActive } from 'app/Main/routes/Viewer/actions';\n\ncornerstoneWebImageLoader.external.cornerstone = cornerstone;\ncornerstoneTools.external.cornerstone = cornerstone;\ncornerstoneTools.external.Hammer = Hammer;\ncornerstoneTools.external.cornerstoneMath = cornerstoneMath;\n\ncornerstoneTools.init({ mouseEnabled: true, showSVGCursors: false, autoResizeViewports: true });\ncornerstoneTools.toolStyle.setToolWidth('4');\n\nconst switchers = [\n { name: '2D', value: '2D' },\n { name: 'Tomo', value: 'tomo' },\n { name: '3DQ', value: '3DQ' },\n];\n\nconst CanvasView = ({\n dataTomo,\n data2D,\n data3DQ,\n activeTool,\n step,\n fullScreen,\n viewsCount,\n setActiveTool,\n updateFinding,\n viewSizesForced,\n currentImageIndex = 0,\n}) => {\n const dicomTypeData = {\n tomo: dataTomo,\n '2D': data2D,\n '3DQ': data3DQ,\n };\n\n const dispatch = useDispatch();\n const isGenuine = activeTool && activeTool.type === 'genuine';\n const [play, setPlay] = useState(false);\n const [showGenuie, setShowGenuie] = useState(true);\n const [imageFetch, setImageFetch] = useState(false);\n const [stackControl, setStackControl] = useState(getDefaultType(step.dataType, dicomTypeData));\n const [viewportState, setViewportState] = useState();\n const element = useRef(null);\n const isFirstRun = useRef(true);\n\n const dicomData = dicomTypeData[stackControl];\n\n const getViewSizes = () => {\n if (viewSizesForced) return viewSizesForced();\n return {\n width: (window.innerWidth - (fullScreen ? 0 : 240)) / viewsCount,\n height: window.innerHeight - 95 - 80,\n };\n };\n\n const onArrowUpdate = image => {\n setTimeout(() => {\n try {\n const viewport = cornerstone.getViewport(element.current);\n const currentImage = viewport.images[image.imageId];\n if (!currentImage) return;\n const { emptyFinding, finding } = currentImage;\n const cuurentArrowData = getToolState(element.current, 'ArrowTool');\n const body = { ...(finding || emptyFinding) };\n body.vectorData = getFindingsCoordinates(cuurentArrowData);\n updateFinding(body);\n } catch (err) {\n console.log(err);\n }\n }, 100);\n };\n\n const updateCornerstone = () => {\n try {\n const viewport = cornerstone.getViewport(element.current);\n if (!viewport) return;\n cornerstone.setViewport(element.current, viewport);\n } catch (err) {\n console.log(err);\n }\n };\n\n const initFindings = () => {\n try {\n cornerstoneTools.addToolForElement(element.current, ArrowTool, {\n onArrowMove: onArrowUpdate,\n onArrowAdd: onArrowUpdate,\n onArrowRemove: onArrowUpdate,\n });\n dispatch(setCanBeActive('findingToggle', false));\n cornerstoneTools.setToolEnabledForElement(element.current, 'ArrowTool');\n setActiveToolEvent();\n setTimeout(updateCornerstone, 1000);\n } catch (err) {\n console.log(err);\n }\n };\n\n const initGenuines = () => {\n try {\n cornerstoneTools.addToolForElement(element.current, GenuineTool, {});\n cornerstoneTools.setToolEnabledForElement(element.current, 'GenuineTool');\n setActiveToolEvent();\n setTimeout(updateCornerstone, 1000);\n } catch (err) {\n console.log(err);\n }\n };\n\n const updateCurrentImageFindings = ({ image }, viewport) => {\n const imageInfo = viewport.images[image.imageId];\n if (!imageInfo) return;\n const findings = imageInfo.finding;\n if (findings && findings.vectorData && !imageInfo.inited) {\n findings.vectorData.forEach(item => {\n cornerstoneTools.addToolState(element.current, 'ArrowTool', createFindingEventData(item));\n });\n }\n imageInfo.inited = true;\n };\n\n const updateCurrentImageGenuine = ({ image }, viewport) => {\n const imageInfo = viewport.images[image.imageId];\n if (!imageInfo) return;\n const points = imageInfo.geniusAIDataList;\n if (points && points.length && !imageInfo.genuineInited) {\n points.forEach(item => {\n cornerstoneTools.addToolState(element.current, 'GenuineTool', createGenuieEventData(item));\n });\n }\n imageInfo.genuineInited = true;\n };\n\n const onImageRendered = (e, v) => {\n try {\n if (!element.current) return;\n const viewport = cornerstone.getViewport(element.current);\n updateCurrentImageFindings(e.detail, viewport);\n updateCurrentImageGenuine(e.detail, viewport);\n setViewportState(viewport);\n } catch (err) {\n console.log(err);\n }\n };\n\n const onWindowResize = () => {\n cornerstone.resize(element.current);\n setElementDisplayPosition();\n };\n\n const setElementDisplayPosition = notMove => {\n const viewport = cornerstone.getViewport(element.current);\n if (!viewport) return;\n // const isLeft = step.position === 'left' && step.thumbpos === 'left';\n const { image, scale } = viewport;\n const { clientWidth } = element.current;\n const imageWidth = image.width; //!stackControl ? image.width : image.width + (isLeft ? -1000 : 0);\n const translation = { x: clientWidth / scale / 2 - imageWidth / 2, y: 0 };\n if (step.position === 'left') translation.x = -translation.x;\n if (!notMove) viewport.translation = translation;\n viewport.initialScale = scale;\n viewport.voi = { windowWidth: 255, windowCenter: 128 };\n cornerstone.setViewport(element.current, viewport);\n };\n\n const setImageStack = element => {\n const imageIds = dicomData.images.map(item => item.url);\n const stack = { currentImageIdIndex: 0, imageIds };\n cornerstoneTools.addStackStateManager(element, ['stack']);\n cornerstoneTools.addToolState(element, 'stack', stack);\n };\n\n const loadImage = async () => {\n if (!element.current) return;\n setImageFetch(true);\n try {\n const images = getDicomImagesObj(dicomData.images);\n const dicomImage = dicomData.images[currentImageIndex] || dicomData.images[0];\n const image = await cornerstone.loadAndCacheImage(dicomImage.url);\n const viewportOptions = { pixelReplication: false, image, dicomImage, images };\n await cornerstone.displayImage(element.current, image, viewportOptions);\n } catch (err) {\n console.log(err);\n } finally {\n setImageFetch(false);\n }\n };\n\n const onRightClick = (el, e) => {\n if (e.which !== 3) return;\n e.preventDefault();\n let tool;\n setActiveTool(item => {\n tool = item;\n return getToolByType('pan');\n });\n\n const handleMouseUp = (tool, e) => {\n if (e.which !== 3) return;\n el.removeEventListener('mouseup', handleMouseUp.bind(null, tool), false);\n const setTool = tool && tool.type && tool.type !== 'reset' ? tool : { type: 'none' };\n setActiveTool(setTool);\n };\n\n el.addEventListener('mouseup', handleMouseUp.bind(null, tool), false);\n // eslint-disable-next-line consistent-return\n return false;\n };\n\n const initElement = async element => {\n try {\n await cornerstone.enable(element);\n await cornerstone.resize(element);\n element.addEventListener('cornerstoneimagerendered', onImageRendered);\n window.addEventListener('resize', onWindowResize);\n element.addEventListener('mousedown', onRightClick.bind(null, element), false);\n element.addEventListener('contextmenu', e => e.preventDefault(), false);\n } catch (err) {\n console.log(err);\n }\n };\n\n const initData = async element => {\n try {\n if (!dicomData) return;\n await element.classList.remove('maximize');\n await setImageStack(element);\n await loadImage();\n await cornerstone.fitToWindow(element);\n await cornerstone.resize(element);\n setElementDisplayPosition();\n setActiveToolEvent();\n setTimeout(initFindings, 100);\n setTimeout(initGenuines, 100);\n } catch (err) {\n console.log(err);\n }\n };\n\n const unmountElement = async () => {\n try {\n element.current.removeEventListener('cornerstoneimagerendered', onImageRendered);\n window.removeEventListener('resize', onWindowResize);\n cornerstone.disable(element.current);\n } catch (err) {\n console.log(err);\n }\n };\n\n const removeTool = name => {\n const { current } = element;\n if (cornerstoneTools.getToolForElement(current, name)) {\n cornerstoneTools.removeToolForElement(current, name);\n }\n };\n\n const setActiveToolEvent = async () => {\n try {\n const viewport = cornerstone.getViewport(element.current);\n if (!viewport || !activeTool.type) return;\n const { type, onTimeAction, noResetFindings } = activeTool;\n const { current } = element;\n current.onclick = null;\n\n removeTool('Pan');\n removeTool('Wwwc');\n removeTool('Zoom');\n removeTool('ZoomMouseWheel');\n\n if (type === 'none' || (onTimeAction && !noResetFindings)) {\n cornerstoneTools.setToolEnabledForElement(current, 'ArrowTool');\n }\n\n if (type === 'reset') {\n current.classList.remove('maximize');\n setTimeout(async () => {\n await cornerstone.resize(current);\n await cornerstone.fitToWindow(current);\n await setElementDisplayPosition();\n });\n } else if (type === 'pan') {\n cornerstoneTools.addToolForElement(current, cornerstoneTools.PanTool);\n cornerstoneTools.setToolActive('Pan', { mouseButtonMask: 1 });\n cornerstoneTools.setToolActive('Pan', { mouseButtonMask: 2 });\n } else if (type === 'zoomInteractive') {\n const options = {\n configuration: {\n invert: false,\n preventZoomOutsideImage: false,\n minScale: 0.01,\n maxScale: 5.0,\n },\n };\n cornerstoneTools.addToolForElement(current, cornerstoneTools.ZoomMouseWheelTool, options);\n cornerstoneTools.addToolForElement(current, cornerstoneTools.ZoomTool, options);\n cornerstoneTools.setToolActive('ZoomMouseWheel', { mouseButtonMask: 1 });\n cornerstoneTools.setToolActive('Zoom', { mouseButtonMask: 1 });\n } else if (type === 'zoomMagnifier') {\n current.onclick = zoomImageMouseClick.bind(null, cornerstone, current, true, false);\n } else if (type === 'windowLevel') {\n cornerstoneTools.addToolForElement(current, cornerstoneTools.WwwcTool);\n cornerstoneTools.setToolActive('Wwwc', { mouseButtonMask: 1 });\n } else if (type === 'finding') {\n cornerstoneTools.setToolActive('ArrowTool', { mouseButtonMask: 1 });\n } else if (type === 'findingToggle') {\n const tool = cornerstoneTools.getToolForElement(current, 'ArrowTool');\n const isDisabled = tool && tool.mode === 'disabled';\n dispatch(setCanBeActive(type, !isDisabled));\n if (isDisabled) {\n cornerstoneTools.setToolEnabledForElement(current, 'ArrowTool');\n } else {\n cornerstoneTools.setToolDisabledForElement(current, 'ArrowTool');\n }\n setTimeout(updateCornerstone, 500);\n } else if (type === 'findingReset') {\n cornerstoneTools.clearToolState(current, 'ArrowTool');\n setTimeout(updateCornerstone, 100);\n } else if (type === 'zoom1_1') {\n current.onclick = () => {\n maximazeElement(cornerstone, current);\n zoomImageMouseClick(cornerstone, current, true, 1);\n };\n }\n } catch (err) {\n console.log(err);\n }\n };\n\n const toggleMax = async () => {\n maximazeElement(cornerstone, element.current, true);\n setElementDisplayPosition(true);\n };\n\n const toggleGenuie = () => {\n if (showGenuie) {\n cornerstoneTools.setToolDisabledForElement(element.current, 'GenuineTool');\n } else {\n cornerstoneTools.setToolEnabledForElement(element.current, 'GenuineTool');\n }\n setTimeout(updateCornerstone, 200);\n setShowGenuie(!showGenuie);\n };\n\n const fullScreenChange = async () => {\n if (!element || !element.current) return;\n try {\n setTimeout(() => {\n cornerstone.resize(element.current);\n cornerstone.fitToWindow(element.current);\n setElementDisplayPosition();\n }, 500);\n } catch (err) {\n console.log(err);\n }\n };\n\n useEffect(() => {\n initElement(element.current);\n return () => unmountElement();\n //eslint-disable-next-line\n }, [stackControl]);\n\n useEffect(() => {\n initData(element.current);\n //eslint-disable-next-line\n }, [dataTomo, step, stackControl]);\n\n useEffect(() => {\n if (element.current) onWindowResize();\n //eslint-disable-next-line\n }, [step]);\n\n useEffect(() => {\n setActiveToolEvent();\n //eslint-disable-next-line\n }, [activeTool]);\n\n useEffect(() => {\n if (isFirstRun.current) {\n isFirstRun.current = false;\n return;\n }\n fullScreenChange();\n //eslint-disable-next-line\n }, [fullScreen]);\n\n const showSlice = element && viewportState && stackControl !== '2D' && !currentImageIndex;\n const hasGenuie = !!dicomData?.images.filter(item => !!item.geniusAIDataList?.length).length;\n\n return (\n \n {hasGenuie && (\n
\n
\n \n \n {showGenuie && (\n
\n {genuieShapes.map((item, i) => {\n const Icon = item.icon;\n const shapesCount = sumShapes(dicomData.images, item.shape);\n return (\n \n \n {!!shapesCount && {shapesCount} }\n \n );\n })}\n
\n )}\n
\n )}\n {viewportState &&
}\n {dicomData && (\n
\n
\n
\n \n \n
\n 0{dicomData.patient.age}Y {dicomData.imageLaterality} {dicomData.viewPosition}\n \n
\n
\n )}\n {step.has_switcher && (\n
\n
\n {switchers.map(item => (\n setStackControl(item.value)}\n className={stackControl === item.value ? 'active' : ''}\n >\n {item.name}\n \n ))}\n
\n
\n )}\n {/* {viewportState && (\n
\n )} */}\n {showSlice && dicomData && (\n
\n )}\n {imageFetch && (\n
\n )}\n {dicomData &&
}\n {!dicomData && (\n
\n No Image Available\n
\n )}\n
\n );\n};\n\nexport default CanvasView;\n","import React, { useEffect, useState, useRef, Fragment } from 'react';\nimport Loading from 'shared/components/Loading';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport { useSnackbar } from 'notistack';\n\nconst Input = ({ forId, type, name, label, value, onChange, className, checked, disabled }) => {\n const inputRef = useRef();\n const active = inputRef.current && inputRef.current.checked;\n\n return (\n \n \n \n {label}\n \n
\n );\n};\n\nconst QuestionaryModal = ({ changeStep, completedSteps, step, lesson, isOpened, caseId }) => {\n const { enqueueSnackbar } = useSnackbar();\n const [initialForm, setInitialForm] = useState(null);\n const [alreadyAnswered, setAlreadyAnswered] = useState(false);\n const [result, setResult] = useState({});\n const isDisabled = completedSteps.length < step.disabled_no_active;\n\n const handleSubmit = async e => {\n e.preventDefault();\n completedSteps.push(9);\n changeStep(10);\n };\n\n const handleChange = (id, value, type, index) => {\n let modifiedValue = '';\n if (type === 'checkbox' && result[id]) {\n let values = result[id].split(',');\n values[index] = value;\n modifiedValue = values.join(',');\n }\n const res = { ...result, [id]: type === 'checkbox' ? modifiedValue : value };\n setResult(res);\n };\n\n const getQuestionary = async () => {\n if (lesson.caseQuestions) {\n const initalQuestion = [\n {\n question_type: 'radio',\n answers: [\n { name: 'No Findings', value: 'a' },\n { name: 'Findings Available', value: 'b', subQuestions: lesson.caseQuestions[caseId] },\n ],\n },\n ];\n setInitialForm(initalQuestion);\n } else if (lesson.episodes) {\n let body = lesson.episodes.filter(item => item.caseId === caseId);\n body = body.sort((a, b) => a.orderNum - b.orderNum);\n if (!body[9]) return;\n try {\n const { id } = body[9];\n const { data } = await Api.get(`/questionary/getepisodequestionaries/${id}`);\n data.data.forEach((group, i) => {\n data.data[i].answers = group.options;\n data.data[i].question_type = group.type;\n data.data[i].answers.forEach((sGroup, sIndex) => {\n sGroup.name = sGroup.key;\n if (sGroup.subQuestions) {\n sGroup.subQuestions.forEach((ssGroup, ssIndex) => {\n ssGroup.answers = ssGroup.options;\n ssGroup.question_type = ssGroup.type;\n ssGroup.name = ssGroup.key;\n });\n }\n });\n });\n setInitialForm(data.data);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n }\n };\n\n const fillUserAnswers = group => {\n if (group.userAnswer) {\n if (!alreadyAnswered) setAlreadyAnswered(true);\n return group.userAnswer;\n }\n if (group.question_type === 'checkbox') {\n return Array(group.answers.length)\n .fill(false)\n .join(',');\n }\n return null;\n };\n\n const initializeResults = () => {\n if (!initialForm) return;\n const result = {};\n initialForm.forEach((group, gIndex) => {\n result[gIndex] = fillUserAnswers(group);\n group.answers.forEach((subGroup, subGroupIndex) => {\n if (subGroup.subQuestions) {\n subGroup.subQuestions.forEach((subGroupOption, i) => {\n result[i] = fillUserAnswers(subGroupOption);\n });\n }\n });\n });\n setResult(result);\n };\n\n const isInputChecked = (subG, subGroup, sGroupIndex, sIndex) => {\n if (subGroup.question_type === 'checkbox') {\n if (result[`sg${sGroupIndex}`]) {\n const arr = result[`sg${sGroupIndex}`].split(',');\n return arr[sIndex] === 'true';\n }\n return false;\n } else {\n return subG.value === result[sGroupIndex];\n }\n };\n\n useEffect(() => {\n if (!isDisabled) getQuestionary();\n //eslint-disable-next-line\n }, []);\n\n useEffect(initializeResults, [initialForm]);\n\n if (isOpened) {\n return (\n \n This step is not required for this case\n
\n );\n }\n\n if (isDisabled) {\n return (\n \n You must first review all steps of the hanging protocol as indicated below\n
\n );\n }\n\n if (!initialForm) return ;\n\n return (\n \n
\n
Score the most suspicious finding \n
\n
\n
\n );\n};\n\nexport default QuestionaryModal;\n","import React from 'react';\n\nconst InformationPart = ({ data, caseId, activeStep }) => {\n const stepComment = data.answers[activeStep] && data.answers[activeStep].title;\n return (\n \n
\n
CASE #{caseId}: \n {data.sections && data.sections.length ? (\n
\n {data &&\n data.sections &&\n data.sections.map((item, i) => {\n return (\n
\n
\n {item.title} \n
\n
\n {item.items &&\n item.items.map((row, i) => {\n return {row.text} ;\n })}\n \n
\n );\n })}\n
\n ) : (\n
\n No information provided for this case\n
\n )}\n
\n
\n {stepComment && (\n
\n {stepComment}\n
\n )}\n
\n
\n );\n};\n\nexport default InformationPart;\n","import step2Icon from 'assets/stepsnew/2n.svg';\nimport step2IconActive from 'assets/stepsnew/2s.svg';\nimport step2IconPassed from 'assets/stepsnew/2p.svg';\nimport step2IconPActive from 'assets/stepsnew/2ps.svg';\n\nimport step3Icon from 'assets/stepsnew/3n.svg';\nimport step3IconActive from 'assets/stepsnew/3s.svg';\nimport step3IconPassed from 'assets/stepsnew/3p.svg';\nimport step3IconPActive from 'assets/stepsnew/3ps.svg';\n\nimport step4Icon from 'assets/stepsnew/4n.svg';\nimport step4IconActive from 'assets/stepsnew/4s.svg';\nimport step4IconPassed from 'assets/stepsnew/4p.svg';\nimport step4IconPActive from 'assets/stepsnew/4ps.svg';\n\nimport tool4Icon from 'assets/tools/4p.svg';\nimport tool4IconActive from 'assets/tools/4a.svg';\nimport tool5Icon from 'assets/tools/5p.svg';\nimport tool5IconActive from 'assets/tools/5a.svg';\nimport tool6Icon from 'assets/tools/6p.svg';\nimport tool6IconActive from 'assets/tools/6a.svg';\nimport tool7Icon from 'assets/tools/7p.svg';\nimport tool7IconActive from 'assets/tools/7a.svg';\nimport tool8Icon from 'assets/tools/8p.svg';\nimport tool8IconActive from 'assets/tools/8a.svg';\nimport tool9Icon from 'assets/tools/9p.svg';\nimport tool9IconActive from 'assets/tools/9a.svg';\n\nconst dicomTypes = {\n '3dq': '3DQ',\n i2d: '2D',\n g2d: '2D',\n r2d: '2D',\n tomo: 'tomo',\n sub: 'tomo',\n};\n\nexport const stepsProperties = {\n RCC: {\n active: true,\n position: 'right',\n thumbpos: 'left',\n has_switcher: false,\n dataType: '2D',\n key: 'RCC',\n },\n LCC: {\n active: true,\n position: 'left',\n thumbpos: 'right',\n has_switcher: false,\n dataType: '2D',\n key: 'LCC',\n },\n RMLO: {\n active: true,\n key: 'RMLO',\n position: 'right',\n thumbpos: 'left',\n has_switcher: false,\n dataType: '2D',\n },\n LMLO: {\n active: true,\n key: 'LMLO',\n position: 'left',\n thumbpos: 'right',\n has_switcher: false,\n dataType: '2D',\n },\n};\n\nexport const answerSteps = [\n {\n id: 1,\n name: '2D CC Views',\n icon: step2Icon,\n iconActive: step2IconActive,\n iconPassed: step2IconPassed,\n iconPassedActive: step2IconPActive,\n },\n {\n id: 2,\n name: '2D MLO Views',\n icon: step3Icon,\n iconActive: step3IconActive,\n iconPassed: step3IconPassed,\n iconPassedActive: step3IconPActive,\n },\n {\n id: 3,\n name: '2D/3D RCC Views',\n icon: step4Icon,\n iconActive: step4IconActive,\n iconPassed: step4IconPassed,\n iconPassedActive: step4IconPActive,\n },\n];\n\nexport const answerTools = [\n {\n name: 'R',\n title: 'Reset Viewers',\n icon: tool4Icon,\n iconActive: tool4IconActive,\n type: 'reset',\n cursor: 'default',\n onTimeAction: true,\n },\n {\n name: 'P',\n title: 'Activate Panning Tool',\n icon: tool5Icon,\n iconActive: tool5IconActive,\n type: 'pan',\n cursor: 'pan',\n onTimeAction: false,\n },\n {\n name: 'I Z',\n title: 'Activate Interactive Zoom',\n icon: tool6Icon,\n iconActive: tool6IconActive,\n type: 'zoomInteractive',\n cursor: 'izoom',\n onTimeAction: false,\n },\n {\n name: 'Z 1:1',\n title: 'Activate 1:1 Tool',\n icon: tool7Icon,\n iconActive: tool7IconActive,\n type: 'zoom1_1',\n cursor: 'fullzoom',\n onTimeAction: false,\n },\n {\n name: 'M',\n title: 'Activate Magnifier Tool',\n icon: tool8Icon,\n iconActive: tool8IconActive,\n type: 'zoomMagnifier',\n cursor: 'mzoom',\n onTimeAction: false,\n },\n {\n name: 'W/L',\n title:\n 'Activate Window/Level Tool (W; for presets move mouse over viewer and press numeric keys 1, 2, 3, ...)',\n icon: tool9Icon,\n iconActive: tool9IconActive,\n type: 'windowLevel',\n cursor: 'wlevel',\n onTimeAction: false,\n },\n];\n\nexport const getToolByType = type => {\n return answerTools.find(item => item.type === type);\n};\n\nexport const mergeSteps = (views, backSteps) => {\n if (!backSteps || !backSteps.length) return [];\n return answerSteps.map((item, index) => {\n const step = backSteps[index];\n item.stepType = step.stepType;\n if (step.stepType === 'case') {\n const left = step.caseLeftFrame;\n const right = step.caseRightFrame;\n const leftDicom = Object.values(views).find(d => d.images.find(i => i.id === left.imageId));\n const rightDicom = Object.values(views).find(d => d.images.find(i => i.id === right.imageId));\n item.views = {};\n if (leftDicom) {\n const { imageLaterality, viewPosition, images, dicomImageType } = leftDicom;\n const imageIndex = images.findIndex(i => i.id === left.imageId);\n const dataType = dicomTypes[dicomImageType];\n const name = `${imageLaterality}${viewPosition}`;\n item.views[`${dataType}_${name}`] = {\n ...stepsProperties[name],\n imageIndex,\n dataType: dataType,\n };\n }\n if (rightDicom) {\n const { imageLaterality, viewPosition, images, dicomImageType } = rightDicom;\n const imageIndex = images.findIndex(i => i.id === right.imageId);\n const dataType = dicomTypes[dicomImageType];\n const name = `${imageLaterality}${viewPosition}`;\n item.views[`${dataType}_${name}`] = {\n ...stepsProperties[name],\n imageIndex,\n dataType: dataType,\n };\n }\n } else {\n item.views = {\n image: step.imageUrl,\n };\n }\n return item;\n });\n};\n","export default __webpack_public_path__ + \"static/media/step1.1b4f2141.png\";","export default __webpack_public_path__ + \"static/media/step2.2422a523.png\";","export default __webpack_public_path__ + \"static/media/step3.03921edc.png\";","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React, { useEffect, useState } from 'react';\nimport { getDefaultType } from '../../../configs';\nimport CanvasView from '../../CanvasView';\nimport ControlArea from '../../ControlArea';\nimport { answerTools } from '../configs';\nimport step1Icon from 'assets/answers/step1.png';\nimport step2Icon from 'assets/answers/step2.png';\nimport step3Icon from 'assets/answers/step3.png';\n\nconst mock = [step1Icon, step2Icon, step3Icon];\n\nconst AnswerViewer = ({ data, mergedSteps, activeStep, setActiveStep }) => {\n const [activeTool, setActiveTool] = useState({});\n const [fullScreen, setFullScreen] = useState(!!document.fullscreenElement);\n\n const fullScreenChange = () => {\n document.addEventListener('fullscreenchange', e => {\n setFullScreen(!!document.fullscreenElement);\n });\n };\n\n useEffect(() => {\n fullScreenChange();\n //eslint-disable-next-line\n }, []);\n\n if (!mergedSteps || !mergedSteps[activeStep]) {\n return (\n \n No answers frame provided for this case\n
\n );\n }\n\n const activeStepData = mergedSteps[activeStep];\n\n const activeStepIsImage = activeStepData && activeStepData.stepType === 'image';\n const stepViews = mergedSteps[activeStep].views;\n const stepViewsData = Object.keys(stepViews);\n const viewsCount = Object.values(stepViews).length;\n\n const viewSizesForced = () => {\n return {\n width: (window.innerWidth - (fullScreen ? 548 : 240 + 530)) / viewsCount,\n height: window.innerHeight - 370,\n };\n };\n\n const getImage = index => {\n try {\n const step = mergedSteps[index];\n const { key, dataType, imageIndex = 0 } = step.views[Object.keys(step.views)[0]];\n const dicomTypeData = {\n tomo: data[`${key}_TOMO`],\n '2D': data[`${key}_2D`],\n '3DQ': data[`${key}_3DQ`],\n };\n\n const control = getDefaultType(dataType, dicomTypeData);\n const dicomData = dicomTypeData[control];\n return step.stepType === 'image' ? step.views.image : dicomData.images[imageIndex].url;\n } catch (err) {\n return mock[index];\n }\n };\n\n return (\n \n
\n
setActiveTool(tool)}\n tools={answerTools}\n disabled={activeStepIsImage}\n />\n \n {stepViewsData.map((key, index) => {\n const item = stepViews[key];\n if (activeStepIsImage) {\n return (\n
\n
;\n
\n );\n }\n return (\n
setActiveTool(tool)}\n fullScreen={fullScreen}\n viewsCount={viewsCount}\n viewSizesForced={viewSizesForced}\n currentImageIndex={item.imageIndex}\n />\n );\n })}\n \n \n
\n {[0, 1, 2].map(index => {\n return (\n
setActiveStep(index)}\n className={`pointer flex-fill ${activeStep === index ? 'active' : ''}`}\n >\n
\n
\n );\n })}\n
\n
\n \n
\n );\n};\n\nexport default AnswerViewer;\n","import React from 'react';\n\nconst DisabledPart = () => {\n return (\n \n You must first review all steps of the hanging protocol as indicated below\n
\n );\n};\n\nexport default DisabledPart;\n","import React, { useEffect, useState } from 'react';\nimport { useSnackbar } from 'notistack';\nimport InformationPart from './components/InformationPart';\nimport AnswerViewer from './components/AnswerViewer';\nimport DisabledPart from './components/DisabledPart';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport Loading from 'shared/components/Loading';\nimport { mergeSteps } from './configs';\n\nconst ExplanationModal = ({ isDisabled, updateStep, caseData, caseId, caseViews }) => {\n const { enqueueSnackbar } = useSnackbar();\n const [fetch, setFetch] = useState(false);\n const [data, setData] = useState();\n const [activeStep, setActiveStep] = useState(0);\n\n const sendUpdate = async () => {\n await updateStep(10);\n };\n\n const getAnswersData = async () => {\n try {\n setFetch(true);\n const { data } = await Api.get(`/cases/getanswers/${caseId}`);\n data.data.mergedSteps = mergeSteps(caseViews, data.data.answers);\n setData(data.data);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetch(false);\n }\n };\n\n useEffect(() => {\n if (!isDisabled) sendUpdate();\n getAnswersData();\n //eslint-disable-next-line\n }, []);\n\n if (isDisabled) return ;\n if (fetch) return ;\n\n if (!data)\n return (\n No Answers Provided for this lesson
\n );\n\n return (\n \n );\n};\n\nexport default ExplanationModal;\n","const findPosition = oElement => {\n if (typeof oElement.offsetParent != 'undefined') {\n for (var posX = 0, posY = 0; oElement; oElement = oElement.offsetParent) {\n posX += oElement.offsetLeft;\n posY += oElement.offsetTop;\n }\n return [posX, posY];\n } else {\n return [oElement.x, oElement.y];\n }\n};\n\nexport const getCoordinates = (e, el) => {\n let PosX = 0;\n let PosY = 0;\n let ImgPos;\n ImgPos = findPosition(el);\n if (!e) e = window.event;\n if (e.pageX || e.pageY) {\n PosX = e.pageX;\n PosY = e.pageY;\n } else if (e.clientX || e.clientY) {\n PosX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;\n PosY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;\n }\n PosX -= ImgPos[0];\n PosY -= ImgPos[1];\n return { x: PosX, y: PosY };\n};\n","import React, { useEffect, useRef, useState } from 'react';\nimport { useSnackbar } from 'notistack';\nimport { getCoordinates } from './utils';\nimport { getError } from 'utils/appHelpers';\nimport Loading from 'shared/components/Loading';\nimport { Api } from 'utils/connectors';\n\nconst getCirclePostions = (pos, imgEl) => {\n if (!pos || !imgEl || !imgEl.current) return {};\n const { naturalWidth, naturalHeight, clientWidth, clientHeight } = imgEl.current;\n const widthScale = pos.x / naturalWidth;\n const heightScale = pos.y / naturalHeight;\n const res = {\n left: widthScale * clientWidth,\n top: heightScale * clientHeight,\n };\n return res;\n};\n\nconst HeatmapModal = ({ caseId, lesson, caseViews, updateStep, isDisabled, isOpened }) => {\n const { enqueueSnackbar } = useSnackbar();\n const [positions, setPositions] = useState();\n const [fetch, setFetching] = useState();\n const [dicom, setDicom] = useState();\n const imgEl = useRef();\n\n const selectText = positions\n ? 'Thank you, your selection is submitted.'\n : 'Please select by one mouse click a most probable calcification you may notice.';\n\n const onMouseDownOnImage = async e => {\n if (positions) return;\n try {\n const cords = getCoordinates(e, imgEl.current);\n const { naturalWidth, naturalHeight, clientWidth, clientHeight } = imgEl.current;\n const widthScale = cords.x / clientWidth;\n const heightScale = cords.y / clientHeight;\n const pos = {\n x: widthScale * naturalWidth,\n y: heightScale * naturalHeight,\n };\n setPositions(pos);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const getHeatMapImage = async () => {\n try {\n setFetching(true);\n if (lesson.caseHeatmaps) {\n const heatmap = lesson.caseHeatmaps[caseId];\n const dicom = Object.values(caseViews).find(i => i.id === heatmap.dicomId);\n if (!dicom) return;\n const image = dicom.images.find(i => i.id === heatmap.imageId);\n dicom.url = image.url;\n if (!isOpened) await updateStep(11);\n setDicom(dicom);\n } else {\n const { data } = await Api.get(`/cases/getheatmaps/${lesson.id}/${caseId}`);\n const dicom = data.data && data.data[0];\n setDicom(dicom);\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetching(false);\n }\n };\n\n useEffect(() => {\n getHeatMapImage();\n //eslint-disable-next-line\n }, []);\n\n if (isDisabled)\n return (\n \n You must first review all steps of the hanging protocol as indicated below\n
\n );\n\n if (fetch) return ;\n\n if (!dicom || isOpened) {\n return (\n \n This step is not required for this case\n
\n );\n }\n\n return (\n \n
\n
Calcification Selection \n \n
\n
{selectText} \n
\n
\n {positions &&
}\n
\n
\n
\n );\n};\n\nexport default HeatmapModal;\n","import React, { useEffect, useState } from 'react';\nimport Loading from 'shared/components/Loading';\nimport { generateViewerData } from './utils';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport { useSnackbar } from 'notistack';\nimport { steps as initialSteps, tools } from './configs';\nimport { useHistory } from 'react-router';\nimport { useSelector } from 'react-redux';\n\nimport ControlArea from './components/ControlArea';\nimport StepsArea from './components/StepsArea';\nimport HelpModal from './components/HelpModal';\nimport CanvasView from './components/CanvasView';\nimport QuestionaryModal from './components/QuestionaryModal';\nimport ExplanationModal from './components/ExplanationModal';\nimport HeatmapModal from './components/HeatmapModal';\n\nconst TypeCasesPlay = ({ lesson, fullData }) => {\n const { enqueueSnackbar } = useSnackbar();\n const history = useHistory();\n const viewerOptions = useSelector(state => state.viewerOptions);\n const [fetch, setFetch] = useState();\n const [activeCase, setActiveCase] = useState(0);\n const [activeStep, setActiveStep] = useState(0);\n const [course, setCourse] = useState(fullData);\n\n const [activeTool, setActiveTool] = useState({});\n const [fullScreen, setFullScreen] = useState(false);\n const [openHelpModal, setOpenHelpModal] = useState(false);\n\n const lessonCases = lesson && (lesson.cases || lesson.activeCases);\n\n const fullScreenChange = () => {\n document.addEventListener('fullscreenchange', e => {\n setFullScreen(!!document.fullscreenElement);\n });\n };\n\n const handelActiveToolChange = tool => {\n setActiveTool(tool);\n };\n\n const stepChangeHandler = async step => {\n const isModal = step === 9 || step === 10 || step === 11;\n if (isModal) setActiveTool({});\n if (!completedSteps.includes(step) && !isModal) {\n completedSteps.push(step);\n }\n setActiveStep(step);\n };\n\n const getCases = async () => {\n try {\n if (!lessonCases || !lessonCases.length) return;\n const { caseId, caseUniqueId } = lessonCases[activeCase];\n const endpoint = `/cases/getcasebyid/${caseUniqueId || caseId}?lut=1&dicomType=all`;\n const { data } = await Api.get(endpoint);\n const cases = generateViewerData(data.data, lesson);\n setCourse({ title: lesson.title, lesson, cases });\n if (!cases[0].completed_steps.includes(0)) {\n cases[0].completed_steps.push(0);\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const onCaseChange = async value => {\n try {\n if (!course.cases[value]) {\n setFetch(true);\n const { caseId, caseUniqueId } = lessonCases[value];\n const endpoint = `/cases/getcasebyid/${caseUniqueId || caseId}?lut=1&dicomType=all`;\n const { data } = await Api.get(endpoint);\n const caseData = generateViewerData(data.data, lesson);\n const cases = [...course.cases, ...caseData];\n if (!cases[value].completed_steps.includes(0)) {\n cases[value].completed_steps.push(0);\n }\n setCourse({ ...course, cases });\n setActiveCase(value);\n setFetch(false);\n } else {\n setActiveCase(value);\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n useEffect(() => {\n fullScreenChange();\n if (!course) getCases();\n //eslint-disable-next-line\n }, []);\n\n if (!course || fetch) return ;\n\n let steps = [...initialSteps];\n const selectedCase = lessonCases?.[activeCase];\n\n if (!!course.isOpened || !selectedCase?.hasHeatmap) {\n // remove questionary and heatmaps\n const excludeNames = course.isOpened\n ? ['question', 'heatmap']\n : !selectedCase?.hasHeatmap\n ? ['heatmap']\n : [];\n steps = steps.filter(item => !excludeNames.includes(item.name));\n }\n\n const isModal = steps[activeStep].type === 'modal';\n const caseViews = course.cases[activeCase].views;\n const completedSteps = course.cases[activeCase].completed_steps;\n const stepViews = steps[activeStep].views;\n const stepViewsData = Object.values(stepViews);\n const viewsCount = Object.values(stepViews).length;\n\n return (\n \n
\n
\n {!isModal &&\n stepViewsData.map((item, index) => (\n {}}\n viewerOptions={viewerOptions}\n />\n ))}\n {isModal && (\n <>\n {steps[activeStep].name === 'question' && (\n \n )}\n {steps[activeStep].name === 'explanator' && (\n {}}\n caseData={selectedCase}\n caseId={selectedCase.caseId}\n caseViews={caseViews}\n />\n )}\n {steps[activeStep].name === 'heatmap' && (\n {\n completedSteps.push(step);\n }}\n />\n )}\n >\n )}\n
\n
\n {openHelpModal &&
setOpenHelpModal(false)} />}\n \n );\n};\n\nexport default TypeCasesPlay;\n","import React from 'react';\nimport { DropDown, DropdownHeader, DropdownBody } from 'shared/components/DropDown';\n\nconst avatarPlaceholder =\n 'https://i1.wp.com/ggrmlawfirm.com/wp-content/uploads/avatar-placeholder.png?fit=256%2C256&ssl=1';\n\nconst AuthorsDropdown = ({ data = [] }) => {\n return (\n \n \n Faculty \n \n \n \n {data.length > 0 &&\n data.map((item, index) => {\n const { name = '', surname, profession = '', imageUrl } = item.authorProfile;\n return (\n \n \n
\n
\n
\n
\n \n {name} {surname}\n \n {profession} \n
\n
\n \n );\n })}\n {!data.length && No results found... }\n \n \n \n );\n};\n\nexport default AuthorsDropdown;\n","import React from 'react';\nimport AuthorsDropdown from './AuthorsDropdown';\nimport BackButton from 'shared/BackButton';\n\nconst HeaderSection = ({ course, hasBack, onBackClick }) => {\n return (\n \n
\n
\n {hasBack && }\n {course.title}\n
\n {!course?.isMarketingMaterial && (\n
\n )}\n
\n
\n );\n};\nexport default HeaderSection;\n","import React, { useEffect, useState } from 'react';\nimport InfoSection from './components/InfoSection';\nimport LessonsListSection from './components/LessonsListSection';\nimport TypeVideo from './components/TypeVIdeo';\nimport TypePDF from './components/TypePDF';\nimport TypeCases from './components/TypeCases';\nimport TypeImage from './components/TypeImage';\nimport TypeEvaluation from './components/TypeEvaluation';\nimport Loading from 'shared/components/Loading';\nimport TagsViewBlock from './components/TagsViewBlock';\nimport TypeCasesPlay from './components/TypeCasesPlay';\nimport EmptyView from './components/EmptyView';\nimport { IconSelectLesson } from 'shared/components/Icons';\nimport HeaderSection from './components/HeaderSection';\n\nconst viewsComponents = {\n 1: TypeVideo,\n 2: TypePDF,\n 3: TypeCases,\n 4: TypeImage,\n};\n\nconst CoursePreview = ({ course }) => {\n const [isViewer, setIsViewer] = useState();\n const [activeLesson, setActiveLesson] = useState();\n\n const onCasePlayClick = () => {\n setIsViewer(true);\n };\n\n useEffect(() => {\n if (course && course.courseLessons) {\n setActiveLesson(course.courseLessons.length && course.courseLessons[0].lesson);\n }\n }, [course]);\n\n if (!course) return ;\n\n const LessonView = activeLesson && viewsComponents[activeLesson.contentType];\n const isFeedback = activeLesson && activeLesson.contentType === 5;\n const noAnyLesson = course.courseLessons?.length < 2 && isFeedback;\n\n if (noAnyLesson) {\n return (\n \n
\n
\n
Please add 1 lesson at least. \n \n
\n );\n }\n\n if (isViewer) {\n return (\n \n {\n setIsViewer(false);\n }}\n />\n \n
\n );\n }\n\n if (isFeedback) {\n return (\n \n {\n setActiveLesson(course.courseLessons.length && course.courseLessons[0].lesson);\n }}\n />\n \n
\n );\n }\n\n return (\n \n
\n
\n {!LessonView && }\n {!!LessonView && }\n
\n
\n
\n );\n};\n\nexport default CoursePreview;\n","import React, { useEffect, useState } from 'react';\nimport { useHistory } from 'react-router';\nimport { connect, useSelector } from 'react-redux';\nimport moment from 'moment';\n\nimport { findCourseFirstLesson, getEndOfSubs, isMobileDevice } from 'utils/appHelpers';\nimport HeaderSection from './components/HeaderSection';\nimport InfoSection from './components/InfoSection';\nimport LessonsListSection from './components/LessonsListSection';\nimport TypeVideo from './components/TypeVIdeo';\nimport TypePDF from './components/TypePDF';\nimport TypeCases from './components/TypeCases';\nimport TypeImage from './components/TypeImage';\nimport TypeQuiz from './components/TypeQuiz';\nimport Loading from 'shared/components/Loading';\nimport CourseFeedback from '../CourseFeedback';\nimport ProgressBar from 'shared/components/ProgressBar';\nimport ExpiredView from './components/ExpiredView';\nimport TagsViewBlock from 'shared/components/TagsViewBlock';\nimport NoAccessCourse from 'shared/components/NoAccessCourse';\n\nimport logo from 'assets/ace-logo-tsp.png';\nimport CourseHeader from '../CourseFeedback/FeedbackHeader';\nimport { getCourses } from '../Courses/actions';\nimport { Api } from 'utils/connectors';\nimport CoursePreview from 'app/PublicCourseView/CoursePreview';\n\nconst viewsComponents = {\n video: TypeVideo,\n pdf: TypePDF,\n cases: TypeCases,\n image: TypeImage,\n quiz: TypeQuiz,\n};\n\nconst CourseView = ({ match, location, getCourses }) => {\n const courses = useSelector(state => state.courses);\n const marketingCourses = useSelector(state => state.marketingCourses);\n const history = useHistory();\n const quizAccessBlocked = location.state?.quizAccessBlocked;\n const needGetCourses = location.state?.needGetCourses;\n const [lesson, setLesson] = useState();\n\n const { courseId, type, lessonId, action } = match.params;\n\n const courseIdOrSharedToken = courseId;\n const isCourseIdShareToken = isNaN(courseId);\n const course = [...courses, ...marketingCourses]?.find(item =>\n isCourseIdShareToken\n ? item.accessToken === courseIdOrSharedToken\n : item.id === Number(courseId),\n );\n\n const [sharedCourse, setSharedCourse] = useState(null);\n\n const onCasePlayClick = () => {};\n\n const getSharedCourseData = async () => {\n try {\n const { data } = await Api.get(`/courses/shared/course/${courseIdOrSharedToken}`);\n sessionStorage.setItem('courseSharedToken', courseIdOrSharedToken);\n setSharedCourse(data.data);\n } catch (err) {\n history.push('/no-web-access');\n }\n };\n\n useEffect(() => {\n if (course) {\n const activeLesson =\n course?.lessons?.find(lesson => lesson.id === Number(lessonId)) || course?.lessons?.[0];\n setLesson(activeLesson);\n if (!type || !lessonId) {\n if (activeLesson) {\n history.replace(`/courses/${courseId}/${activeLesson.type}/${activeLesson.id}`);\n }\n }\n }\n //eslint-disable-next-line\n }, [match.params]);\n\n useEffect(() => {\n if (isCourseIdShareToken) {\n getSharedCourseData();\n }\n }, [courseIdOrSharedToken]);\n\n useEffect(() => {\n if (needGetCourses) getCourses();\n }, [needGetCourses]);\n\n if (isCourseIdShareToken) return ;\n\n if (!course && !isCourseIdShareToken) return ;\n\n const { expirable, expire, isOpened } = course;\n const isExpired = expirable && expire ? moment(expire).diff(new Date()) <= 0 : false;\n\n if ((!type || !lessonId) && !courseIdOrSharedToken) {\n findCourseFirstLesson(course, history);\n return null;\n }\n\n if (isExpired)\n return (\n \n \n \n
\n );\n\n if (type === 'feedback') {\n return ;\n }\n\n if (type === 'quiz' && quizAccessBlocked) {\n return (\n \n
\n
You need to complete all lessons to access quiz.
\n
\n );\n }\n\n if (\n ((!lesson || lesson.id !== Number(lessonId)) && !isCourseIdShareToken) ||\n (isCourseIdShareToken && !course)\n ) {\n return ;\n }\n\n const LessonView = viewsComponents[type];\n\n if (course?.isMarketingMaterial && isMobileDevice()) {\n return (\n \n
\n
\n
\n
\n
\n
\n
\n {!!course?.courseLessons?.[0]?.lesson && (\n
\n )}\n
\n );\n }\n\n return (\n \n
\n
\n \n
\n
\n
\n \n
\n
\n
\n
\n {!isOpened && (\n
\n )}\n
{getEndOfSubs(course.expire, course.expirable)}
\n
\n
\n
\n
\n
\n );\n};\n\nconst mapStateToProps = state => ({\n courses: state.courses,\n});\n\nconst mapDispatchToProps = {\n getCourses,\n};\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(CourseView);\n","import React, { useState } from 'react';\nimport { connect } from 'react-redux';\nimport AuthorBox from 'shared/components/AuthorBox';\nimport { IconAuthorsNew } from 'shared/components/Icons';\n\nconst Authors = ({ authors }) => {\n const [active, setActive] = useState();\n\n const onSeeMoreClick = (index, e) => {\n e.preventDefault();\n setActive(active === index ? '' : index);\n };\n\n return (\n <>\n \n
\n \n \n \n Faculty\n
\n
\n \n
\n {authors &&\n authors.map((item, index) => (\n
\n ))}\n
\n
\n >\n );\n};\n\nconst mapStateToProps = ({ authors }) => ({\n authors,\n});\n\nexport default connect(\n mapStateToProps,\n {},\n)(Authors);\n","import SideBar from 'app/Main/components/SideBar';\nimport React from 'react';\n\nconst PublicWrapper = ({ children, hasNotNav }) => {\n return (\n \n
\n {!hasNotNav && (\n \n \n \n )}\n \n {children}\n \n
\n
\n );\n};\n\nexport default PublicWrapper;\n","/* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */\nimport React from 'react';\nimport { IconAbout } from 'shared/components/Icons';\nimport { confirmAlert } from 'react-confirm-alert';\nimport CopyRightInfo from 'shared/components/CopyRightInfo';\nimport PublicWrapper from 'shared/components/layouts/PublicWrapper';\nimport { useSelector } from 'react-redux';\n\nconst specialities = [\n {\n id: 1,\n name: 'GYN SURGICAL',\n desc:\n 'Minimally invasive GYN treatment options for women who suffer from abnormal uterine bleeding.',\n img: 'https://hologiced.com/wp-content/uploads/2018/06/uterus-1-1.png',\n link: 'https://hologiced.com/gyn-surgical/',\n },\n {\n id: 2,\n name: 'DIAGNOSTIC',\n desc:\n 'Innovative technology for cervical cancer screening, testing for STIs and vulvovaginal infections, and preterm labor assessment.',\n img: 'https://hologiced.com/wp-content/uploads/2018/06/diagnostic-icon.svg',\n link: 'https://hologiced.com/diagnostic/',\n },\n {\n id: 3,\n name: 'BREAST',\n desc:\n 'Life-saving solutions accelerated by cutting edge science for early detection, more certain diagnosis and accurate, compassionate treatment',\n img: 'https://hologiced.com/wp-content/uploads/2018/06/breast-icon.svg',\n link: 'https://hologiced.com/breast-health/',\n },\n {\n id: 4,\n name: 'SKELETAL',\n desc:\n 'Clinical images that enable doctors to make more informed diagnoses and treatment decisions in time to have a positive impact on patient’s health.',\n img: 'https://hologiced.com/wp-content/uploads/2018/06/skeletal-icon.svg',\n link: 'https://hologiced.com/skeletal-health/',\n },\n];\n\nconst About = () => {\n const isAuth = useSelector(state => state.isAuthenticated);\n\n const goWithLink = (url, e) => {\n e.preventDefault();\n if (!url) return;\n confirmAlert({\n message:\n 'A new browser tab will now be opened. You can close it at any time and come back to this tab to continue.',\n buttons: [\n {\n label: 'Continue',\n onClick: () => window.open(url, '_blank'),\n },\n {\n label: 'Close',\n },\n ],\n });\n };\n\n const Wrapper = isAuth ? React.Fragment : PublicWrapper;\n\n return (\n \n \n
\n \n \n \n About\n
\n
\n \n
\n
\n The mission of Hologic Medical Education is to improve patient care through excellence\n in education, communication of clinical and scientific evidence, and partnerships with\n the healthcare community.\n
\n
\n
\n AREAS OF SPECIALTY \n
\n
\n {specialities.map(item => (\n
\n
\n
\n
\n
\n {item.name}\n \n
{item.desc}
\n
\n ))}\n
\n
\n \n \n );\n};\n\nexport default About;\n","import React, { useState } from 'react';\nimport { useSelector } from 'react-redux';\nimport { useSnackbar } from 'notistack';\nimport { IconContacts } from 'shared/components/Icons';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport ButtonLine from 'shared/components/ButtonLine';\nimport Select from 'shared/components/Select';\n\nconst categories = [\n {\n name: 'General Inquiry / Feedback',\n code: '1',\n },\n {\n name: 'Support Ticket',\n code: '2',\n },\n];\n\nconst Contacts = () => {\n const account = useSelector(state => state.account);\n const initData = { name: account.fullName, email: account.email, text: '', ticket: '1' };\n const { enqueueSnackbar, closeSnackbar } = useSnackbar();\n const [fetch, setFetch] = useState(false);\n const [data, setData] = useState(initData);\n\n const action = key => (\n {\n closeSnackbar(key);\n }}\n >\n OK\n \n );\n\n const handleSubmit = async e => {\n e.preventDefault();\n try {\n setFetch(true);\n data.ticket = Number(data.ticket);\n await Api.post('/common/sendemail', data);\n enqueueSnackbar('Your message has been successfully submitted.', {\n variant: 'success',\n persist: true,\n action,\n });\n setData(initData);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetch(false);\n }\n };\n\n const handleChange = ({ target }) => {\n const { name, value } = target;\n const tempData = { ...data };\n tempData[name] = value;\n setData(tempData);\n };\n\n return (\n \n
\n
\n \n \n \n Contact\n
\n
\n
\n
\n
\n {/*
\n
\n Didn't find a solution to your problem? \n For US customers please contact through the support form or direct email at{' '}\n \n Selenia.Support@hologic.com\n \n . \n \n For Australia or New Zealand customers please contact through the support form {' '}\n or direct email at{' '}\n \n ANZonlinevirtualtomo@hologic.com\n \n . \n
\n
© 2013-2020 Hologic, Inc.
\n
*/}\n
\n
\n
\n );\n};\n\nexport default Contacts;\n","import React, { useState, useEffect, useCallback } from 'react';\nimport BackButton from 'shared/BackButton';\nimport { useSnackbar } from 'notistack';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport { IconSearch } from 'shared/components/Icons';\nimport ReactPaginate from 'react-paginate';\nimport moment from 'moment';\n\nconst limit = 8;\n\nconst Messages = ({ history }) => {\n const { enqueueSnackbar } = useSnackbar();\n const [fetch, setFetch] = useState(false);\n const [data, setData] = useState(null);\n const [page, setPage] = useState(0);\n const [pageCount, setPageCount] = useState(0);\n\n const getMessages = useCallback(async () => {\n try {\n setFetch(true);\n const params = { page: page + 1, limit };\n const { data } = await Api.get('/common/getusercontacts', { params });\n setData(data.data.results);\n setPageCount(Math.ceil(data.data.count / limit));\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n setPageCount(0);\n } finally {\n setFetch(false);\n }\n }, [enqueueSnackbar, page]);\n\n const onPageChange = ({ selected }) => {\n setPage(selected);\n };\n\n useEffect(() => {\n getMessages();\n }, [page, getMessages]);\n\n return (\n \n
\n
\n \n My Requests \n
\n
\n
\n
\n \n \n \n Date
\n \n \n Ticket Number
\n \n \n Name
\n \n \n Reply-To
\n \n \n Category
\n \n \n Description
\n \n \n \n \n {data && data.length > 0 ? (\n data.map((item, index) => {\n return (\n \n \n \n {item.createdAt ? moment(item.createdAt).format('MM/DD/YYYY') : ''}\n
\n \n \n {item.ticketSeq || '-'}
\n \n \n {item.name || 'Unknown'}
\n \n \n {item.email || '-'}
\n \n \n {item.category || '-'}
\n \n \n {item.text}
\n \n \n );\n })\n ) : (\n \n \n \n No Results Found\n
\n \n \n )}\n \n
\n {pageCount > 1 && (\n
\n )}\n
\n
\n );\n};\n\nexport default Messages;\n","import React, { useEffect, useState } from 'react';\nimport { IconEducation } from 'shared/components/Icons';\nimport { Api } from 'utils/connectors';\nimport Loading from 'shared/components/Loading';\n\nconst PrivacyAndPolicy = () => {\n const [html, setHtml] = useState();\n\n const getPrivacyData = async () => {\n const { data } = await Api.get('/common/privacy');\n setHtml(data);\n };\n\n useEffect(() => {\n getPrivacyData();\n }, []);\n\n if (!html) return ;\n\n return (\n <>\n \n
\n \n \n \n Privacy and Policy\n
\n
\n \n >\n );\n};\n\nexport default PrivacyAndPolicy;\n","import React from 'react';\n\nimport iconMagnifier from 'assets/tools/8p.svg';\nimport iconToggle from 'assets/tools/7p.svg';\nimport iconInteractiveZoom from 'assets/tools/6p.svg';\nimport iconMove from 'assets/tools/5p.svg';\nimport iconReset from 'assets/tools/4p.svg';\nimport iconBrightness from 'assets/tools/9p.svg';\nimport iconFinding from 'assets/tools/2p.svg';\nimport iconMaximize from 'assets/arrows/2p.svg';\nimport iconMinimize from 'assets/arrows/4p.svg';\nimport iconStudyOverview from 'assets/stepsnew/1n.svg';\nimport iconCC from 'assets/stepsnew/2n.svg';\nimport iconMLO from 'assets/stepsnew/3n.svg';\nimport iconCCRight2D from 'assets/stepsnew/4n.svg';\nimport iconMLORight2D from 'assets/stepsnew/5n.svg';\nimport iconCCLeft2D from 'assets/stepsnew/6n.svg';\nimport iconMLOLeft2D from 'assets/stepsnew/7n.svg';\nimport iconRightBrest from 'assets/stepsnew/8n.svg';\nimport iconLeftBrest from 'assets/stepsnew/9n.svg';\nimport iconAssessment from 'assets/stepsnew/11n.svg';\nimport iconDiagnostic from 'assets/stepsnew/heatmapn.svg';\nimport iconComments from 'assets/stepsnew/12n.svg';\n\n// Temporary Icons\n// import iconStructuredRoaming from 'assets/temp_icons/icon_quadrant_roaming.png';\n// import iconInvert from 'assets/tools/9p.svg';\n// import iconStack from 'assets/temp_icons/icon_stack.png';\nimport iconFindingsShow from 'assets/tools/1p.svg';\nimport iconFindingsDelete from 'assets/tools/10p.svg';\nimport iconSteps from 'assets/stepchange.png';\nimport iconCases from 'assets/casechange.png';\n// contact, print\n\nexport const viewerDialogJson = {\n zoomAndMagnificationTool: [\n {\n description: 'Magnifier',\n icon: iconMagnifier,\n buttons: [], // ['R'],\n },\n {\n description: 'Interactive zoom',\n icon: iconInteractiveZoom,\n buttons: [], // ['Z'],\n },\n {\n description:\n 'Full resolution mode: Clicking on the image will show in original full resolution. By clicking on it again, will toggle back to fit to viewer mode.',\n icon: iconToggle,\n buttons: [], // ['Shift', 'Z'],\n },\n {\n description: 'Maximize viewer',\n icon: iconMaximize,\n buttons: [], // ['Shift', 'M'],\n },\n // {\n // description:\n // 'Structured roaming: Moves through zoomed views to make sure the whole tissue is displayed.',\n // icon: iconStructuredRoaming,\n // buttons: [], // ['TAB'],\n // },\n {\n description: 'Panning/moving image (also via right mouse button)',\n icon: iconMove,\n buttons: [], // ['P'],\n },\n {\n description: 'Reset viewers: Changes zoom and window/level to initial values',\n icon: iconReset,\n buttons: [], // ['Shift', 'R'],\n },\n ],\n otherTools: [\n {\n description: 'Change window/level settings',\n icon: iconBrightness,\n buttons: [], // ['W'],\n },\n // {\n // description: 'Invert images',\n // icon: iconInvert,\n // buttons: [], // ['I'],\n // },\n // {\n // description: 'Stacking/Slicing (also via ↑ / ↓ or scroll wheel) ',\n // icon: iconStack,\n // buttons: [], // ['↑', '↓', 'S'],\n // },\n {\n description: 'Finding tool: Allows to place arrows at regions of interest.',\n icon: iconFinding,\n buttons: [], // ['F'],\n },\n {\n description: 'Show Markings',\n icon: iconFindingsShow,\n buttons: [], // ['Shift', 'F'],\n },\n {\n description: 'Delete Markings',\n icon: iconFindingsDelete,\n buttons: [], // ['Shift', 'F'],\n },\n ],\n navigation: [\n {\n description: 'Full screen on',\n icon: iconMaximize,\n buttons: [], // ['0'],\n },\n {\n description: 'Full screen off',\n icon: iconMinimize,\n buttons: [], // ['0', 'Esc'],\n isSlash: true,\n },\n {\n description:\n 'Hanging Protocol: Right (next) and left (prior) icons to step you through the images for that patient',\n icon: iconSteps,\n buttons: [], // ['←', '→'],\n width: '130px',\n isSlash: true,\n },\n {\n description:\n 'Case Navigation: Right (next) and left (prior) icons to navigate through the course',\n icon: iconCases,\n buttons: [],\n width: '130px',\n },\n ],\n hangingProtocols: [\n {\n className: 'col-4',\n childs: [\n {\n description: 'Study overview',\n icon: iconStudyOverview,\n buttons: [],\n width: '70px',\n },\n {\n description: 'CC Right/Left',\n icon: iconCC,\n buttons: [],\n },\n {\n description: 'MLO Right/Left',\n icon: iconMLO,\n buttons: [],\n },\n {\n description: 'CC Right 2D/Tomo',\n icon: iconCCRight2D,\n buttons: [],\n },\n {\n description: 'MLO Right 2D/Tomo',\n icon: iconMLORight2D,\n buttons: [],\n },\n {\n description: 'CC Left 2D/Tomo',\n icon: iconCCLeft2D,\n buttons: [],\n },\n {\n description: 'MLO Left 2D/Tomo',\n icon: iconMLOLeft2D,\n buttons: [],\n },\n ],\n },\n {\n className: 'flex-fill',\n childs: [\n {\n description: 'Right breast of interest',\n icon: iconRightBrest,\n buttons: [],\n },\n {\n description: 'Left breast of interest',\n icon: iconLeftBrest,\n buttons: [],\n },\n {\n description: 'Assessment (if available)',\n icon: iconAssessment,\n buttons: [],\n },\n {\n description: 'Expert’s findings and comments',\n icon: iconComments,\n buttons: [],\n },\n {\n description: 'Diagnostic images (if available)',\n icon: iconDiagnostic,\n buttons: [],\n },\n ],\n },\n ],\n caseAccessment: [\n Cases may be assessed only when all mandatory hangings were reviewed. ,\n \n Open the reporting page via click on\n \n or as part of the workflow.\n ,\n Note that you should report only the most suspicious finding per case. ,\n \n If \"Finding\" was selected, all corresponding subcategories need to be filled in before\n submission is possible.\n ,\n Submitted assessments cannot be changed afterwards. ,\n \n For comparison open experts' assessment after your assessment via{' '}\n .\n ,\n ],\n faq: [\n {\n q: 'What to do if I cannot open the survey?',\n a: '',\n variants: [\n 'Make sure that all previous course modules are completed and all case assessments were submitted (40 of 40)',\n 'Reload page (F5) if multiple tabs were used',\n ],\n },\n {\n q: 'What to do if my scrolling direction seems to be contra-intuitively for me?',\n a:\n 'This behavior is usually different on Windows PCs and Mac computers. If you are using a Mac, you may select the scroll direction as one of the mouse settings.',\n },\n {\n q: 'How to proceed if initial system tests fail?',\n a: 'Click on corresponding \"more\" links in the system test window for advice',\n },\n {\n q: 'How to reset a browser zoom? (e.g., if changed via Ctrl + scroll wheel)',\n a: 'Press (Ctrl + 0)',\n },\n {\n q: 'What to do if “exit fullscreen” annoys me in Chrome?',\n a: 'Use FireFox Version 31.0 or newer',\n },\n {\n q: 'What to do if IE11 fullscreen zero key does not work?',\n a: 'Use FireFox Version 31.0 or newer',\n },\n {\n q: 'What to do if in IE11 the mouse cursor for tools is not updated?',\n a: 'Use FireFox Version 31.0 or newer',\n },\n {\n q: 'How to control cine mode on tomo images?',\n a: 'Use context specific controlling options:',\n variants: [\n 'To start cine on all displayed tomo images: Click on the cine button of the toolbar',\n 'To start cine mode on one tomo image only: Use the context menu (via right mouse button)',\n 'Note: Space bar will start/stop cine mode in all viewers if the mouse cursor is outside of them, and in the focus viewer only, if the mouse cursor is within one of the viewers',\n ],\n },\n {\n q: 'What to do if volume control in firefox (31.0) does not work for videos?',\n a:\n 'Either use the volume control of the system in the lower right corner, or change the video volume control as usual, the volume will change, although the display of the volume control will not change. ',\n },\n ],\n};\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { zoomAndMagnificationTool } = viewerDialogJson;\n\nconst ZoomAndMagnificationTools = () => (\n \n
\n Generally, a double click into an image will magnify it-usually to the available size of the\n viewer. Mammograms\n are magnified to 1;1,i.e., original size.\n
\n {zoomAndMagnificationTool.map(({ icon, description, buttons, note }, index) => {\n return (\n
\n \n {description} \n {buttons.map((btn, index) => (\n {btn} \n ))}\n
\n );\n })}\n
\n);\n\nexport default ZoomAndMagnificationTools;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { otherTools } = viewerDialogJson;\n\nconst OtherTools = () => (\n \n {otherTools.map(({ icon, description, buttons }, index) => {\n return (\n
\n \n {description} \n {buttons.map((btn, index) => (\n {btn} \n ))}\n
\n );\n })}\n
\n);\n\nexport default OtherTools;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { navigation } = viewerDialogJson;\n\nconst Navigation = () => (\n \n {navigation.map(({ icon, description, buttons = [], isSlash, width }, index) => {\n return (\n
\n \n {description} \n {buttons.map((btn, index) => (\n \n {btn}\n \n ))}\n
\n );\n })}\n
\n);\n\nexport default Navigation;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { hangingProtocols } = viewerDialogJson;\n\nconst HangingProtocols = () => (\n \n
\n {hangingProtocols.map((group, groupIndex) => (\n
\n {group.childs.map(({ icon, description, buttons = [], isSlash, width }, childIndex) => {\n return (\n
\n \n {description} \n {buttons.map((btn, btnIndex) => (\n {btn} \n ))}\n
\n );\n })}\n
\n ))}\n
\n
\n Note : The current (screening) study is always displayed first.\n
\n
\n For hangings with viewers with multiple images: All images in a hanging can be toggled by\n clicking the tab icon.\n
\n
\n);\n\nexport default HangingProtocols;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { caseAccessment } = viewerDialogJson;\n\nconst CaseAccessment = () => {\n return (\n \n {caseAccessment.map((item, index) => (\n \n {item}\n \n ))}\n \n );\n};\n\nexport default CaseAccessment;\n","import React from 'react';\nimport { viewerDialogJson } from './../config/consts';\nconst { faq } = viewerDialogJson;\n\nconst FAQ = () => {\n return (\n \n {faq.map(({ q, a, variants }, qIndex) => (\n
\n
\n Q: {q} \n
\n
A: {a}
\n {variants && variants.length > 0 && (\n
\n {variants.map((variant, vIndex) => (\n {variant} \n ))}\n \n )}\n
\n ))}\n
\n );\n};\n\nexport default FAQ;\n","/* eslint-disable react/jsx-no-duplicate-props */\n/* eslint-disable jsx-a11y/anchor-is-valid */\nimport { HOST } from 'configs';\nimport React from 'react';\n\nconst Print = () => {\n return (\n \n );\n};\n\nexport default Print;\n","import React, { useState } from 'react';\nimport Modal from 'react-modal';\nimport { AppBar, Tabs, Tab } from '@material-ui/core';\nimport {\n ZoomAndMagnificationTools,\n OtherTools,\n Navigation,\n HangingProtocols,\n CaseAccessment,\n // FAQ,\n Print,\n} from './components';\n\nconst modalStyles = {\n overlay: {\n // backgroundColor: '#000000',\n backgroundColor: 'rgba(0, 0, 0, 0.75)',\n },\n};\n\nconst tabs = [\n {\n label: 'Zoom and Magnification Tools',\n component: ,\n },\n {\n label: 'Other Tools',\n component: ,\n },\n {\n label: 'Navigation',\n component: ,\n },\n {\n label: 'Hanging Protocols',\n component: ,\n },\n {\n label: 'Case Accessment',\n component: ,\n },\n // {\n // label: 'FAQ',\n // component: ,\n // },\n {\n label: 'Print',\n component: ,\n },\n];\n\nconst HelpModal = ({ onModalClose }) => {\n const [tab, setTab] = useState(0);\n\n const handleTabChange = (e, index) => {\n setTab(index);\n };\n\n return (\n \n \n Radiologist Training - Online Help \n \n \n {tabs.map(({ label, component }, index) => (\n \n ))}\n \n \n {tabs[tab].component}
\n \n );\n};\n\nexport default HelpModal;\n","import React, { useEffect, useState, useRef, Fragment } from 'react';\nimport { useSnackbar } from 'notistack';\nimport { Api } from 'utils/connectors';\nimport Loading from 'shared/components/Loading';\nimport { getError } from 'utils/appHelpers';\n\nconst Input = ({ forId, type, name, label, value, onChange, className, checked, disabled }) => {\n const inputRef = useRef();\n const active = inputRef.current && inputRef.current.checked;\n\n return (\n \n \n \n {label}\n \n
\n );\n};\n\nconst QuestionaryModal = ({\n episodes,\n lessonId,\n changeStep,\n completedSteps,\n step,\n caseId,\n isOpened,\n updateLessonsEpisode,\n course,\n}) => {\n const { enqueueSnackbar } = useSnackbar();\n const [initialForm, setInitialForm] = useState(null);\n const [alreadyAnswered, setAlreadyAnswered] = useState(false);\n const [result, setResult] = useState({});\n const isDisabled = completedSteps.length < step.disabled_no_active;\n\n const handleSubmit = async e => {\n e.preventDefault();\n const answers = [];\n // eslint-disable-next-line no-unused-vars\n for (let key in result) {\n answers.push({\n questionaryId: key,\n answer: result[key],\n });\n }\n const body = { lessonId, completed: 1, step: 10, answers, caseId, courseId: course.id };\n await Api.post('/courses/updateuserlesson', body);\n await updateLessonsEpisode(9);\n completedSteps.push(9);\n changeStep(10);\n };\n\n const handleChange = (id, value, type, index) => {\n let modifiedValue = '';\n if (type === 'checkbox') {\n let values = result[id].split(',');\n values[index] = value;\n modifiedValue = values.join(',');\n }\n const res = { ...result, [id]: type === 'checkbox' ? modifiedValue : value };\n setResult(res);\n };\n\n const getQuestionary = async () => {\n let body = episodes.filter(item => item.caseId === caseId);\n body = body.sort((a, b) => a.orderNum - b.orderNum);\n if (!body[9]) return;\n try {\n const { id } = body[9];\n const { data } = await Api.get(`/questionary/getepisodequestionaries/${id}`);\n setInitialForm(data.data);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const fillUserAnswers = group => {\n if (group.userAnswer) {\n if (!alreadyAnswered) setAlreadyAnswered(true);\n return group.userAnswer;\n }\n if (group.type === 'checkbox') {\n return Array(group.options.length)\n .fill(false)\n .join(',');\n }\n return null;\n };\n\n const initializeResults = () => {\n if (!initialForm) return;\n const result = {};\n initialForm.forEach(group => {\n result[group.id] = fillUserAnswers(group);\n group.options.forEach(subGroup => {\n if (subGroup.subQuestions) {\n subGroup.subQuestions.forEach(subGroupOption => {\n result[subGroupOption.id] = fillUserAnswers(subGroupOption);\n });\n }\n });\n });\n setResult(result);\n };\n\n const isInputChecked = (subG, subGroup, sIndex) => {\n if (subGroup.type === 'checkbox') {\n const arr = result[subGroup.id].split(',');\n return arr[sIndex] === 'true';\n } else {\n return subG.value === result[subGroup.id];\n }\n };\n\n useEffect(() => {\n if (!isDisabled) getQuestionary();\n //eslint-disable-next-line\n }, []);\n\n useEffect(initializeResults, [initialForm]);\n\n if (isOpened) {\n return (\n \n This step is not required for this case\n
\n );\n }\n\n if (isDisabled) {\n return (\n \n You must first review all steps of the hanging protocol as indicated below\n
\n );\n }\n\n if (!initialForm) return ;\n\n return (\n \n
\n
Score the most suspicious finding \n
\n \n {initialForm.length > 0 &&\n initialForm.map((group, gIndex) => {\n // LEVEL 1\n return (\n
\n {group.options &&\n group.options.map((groupOption, gOptIndex) => {\n const checked = groupOption.value === result[group.id];\n const isCorrect = group.correct === groupOption.value;\n // ITEM ON LEVEL 1\n return (\n \n \n handleChange(\n group.id,\n group.type === 'radio' ? e.target.value : e.target.checked,\n group.type,\n gOptIndex,\n )\n }\n label={groupOption.key}\n className={`${isCorrect && alreadyAnswered ? 'a-correct' : ''} ${\n checked && !isCorrect && alreadyAnswered ? 'a-incorrect' : ''\n } question-parent d-flex align-items-center`}\n />\n {groupOption.subQuestions &&\n groupOption.subQuestions.length > 0 &&\n result[group.id] === groupOption.value && (\n \n {groupOption.subQuestions.map((subGroup, sGroupIndex) => {\n // LEVEL 2\n return (\n
\n {subGroup.options &&\n subGroup.options.map((subG, sIndex) => {\n let isCorrect = subGroup.correct === subG.value;\n let checked = isInputChecked(subG, subGroup, sIndex);\n const isCheckBox = subGroup.type === 'checkbox';\n if (isCheckBox) {\n isCorrect =\n subGroup.correct &&\n subGroup.correct.split(',')[sIndex] === 'true';\n }\n // ITEM ON LEVEL 2\n return (\n \n \n handleChange(\n subGroup.id,\n subGroup.type === 'radio'\n ? e.target.value\n : e.target.checked,\n subGroup.type,\n sIndex,\n )\n }\n className={`${\n isCorrect && alreadyAnswered\n ? `a-correct${isCheckBox ? '-check' : ''}`\n : ''\n } ${\n checked && !isCorrect && alreadyAnswered\n ? `a-incorrect${isCheckBox ? '-check' : ''}`\n : ''\n } question-child`}\n />\n \n );\n })}\n
\n );\n })}\n
\n )}\n \n );\n })}\n \n );\n })}\n
\n \n \n Submit Assessment\n \n
\n \n
\n
\n );\n};\n\nexport default QuestionaryModal;\n","import React from 'react';\n\nconst InformationPart = ({ data, caseId, activeStep }) => {\n const stepComment = data.answers[activeStep] && data.answers[activeStep].title;\n return (\n \n
\n
CASE #{caseId}: \n {data.sections && data.sections.length ? (\n
\n {data &&\n data.sections &&\n data.sections.map((item, i) => {\n return (\n
\n
\n {item.title} \n
\n
\n {item.items &&\n item.items.map((row, i) => {\n return {row.text} ;\n })}\n \n
\n );\n })}\n
\n ) : (\n
\n No information provided for this case\n
\n )}\n
\n
\n {stepComment && (\n
\n {stepComment}\n
\n )}\n
\n
\n );\n};\n\nexport default InformationPart;\n","import step1Icon from 'assets/stepsnew/1n.svg';\nimport step1IconActive from 'assets/stepsnew/1s.svg';\nimport step1IconPassed from 'assets/stepsnew/1p.svg';\nimport step1IconPActive from 'assets/stepsnew/1ps.svg';\n\nimport step2Icon from 'assets/stepsnew/2n.svg';\nimport step2IconActive from 'assets/stepsnew/2s.svg';\nimport step2IconPassed from 'assets/stepsnew/2p.svg';\nimport step2IconPActive from 'assets/stepsnew/2ps.svg';\n\nimport step3Icon from 'assets/stepsnew/3n.svg';\nimport step3IconActive from 'assets/stepsnew/3s.svg';\nimport step3IconPassed from 'assets/stepsnew/3p.svg';\nimport step3IconPActive from 'assets/stepsnew/3ps.svg';\n\nimport step4Icon from 'assets/stepsnew/4n.svg';\nimport step4IconActive from 'assets/stepsnew/4s.svg';\nimport step4IconPassed from 'assets/stepsnew/4p.svg';\nimport step4IconPActive from 'assets/stepsnew/4ps.svg';\n\nimport step5Icon from 'assets/stepsnew/5n.svg';\nimport step5IconActive from 'assets/stepsnew/5s.svg';\nimport step5IconPassed from 'assets/stepsnew/5p.svg';\nimport step5IconPActive from 'assets/stepsnew/5ps.svg';\n\nimport step6Icon from 'assets/stepsnew/6n.svg';\nimport step6IconActive from 'assets/stepsnew/6s.svg';\nimport step6IconPassed from 'assets/stepsnew/6p.svg';\nimport step6IconPActive from 'assets/stepsnew/6ps.svg';\n\nimport step7Icon from 'assets/stepsnew/7n.svg';\nimport step7IconActive from 'assets/stepsnew/7s.svg';\nimport step7IconPassed from 'assets/stepsnew/7p.svg';\nimport step7IconPActive from 'assets/stepsnew/7ps.svg';\n\nimport step8Icon from 'assets/stepsnew/8n.svg';\nimport step8IconActive from 'assets/stepsnew/8s.svg';\nimport step8IconPassed from 'assets/stepsnew/8p.svg';\nimport step8IconPActive from 'assets/stepsnew/8ps.svg';\n\nimport step9Icon from 'assets/stepsnew/9n.svg';\nimport step9IconActive from 'assets/stepsnew/9s.svg';\nimport step9IconPassed from 'assets/stepsnew/9p.svg';\nimport step9IconPActive from 'assets/stepsnew/9ps.svg';\n\nimport step11Icon from 'assets/stepsnew/11n.svg';\nimport step11IconActive from 'assets/stepsnew/11s.svg';\nimport step11IconPassed from 'assets/stepsnew/11p.svg';\nimport step11IconPActive from 'assets/stepsnew/11ps.svg';\n\n// import step10Icon from 'assets/steps/11p.svg';\n// import step10IconActive from 'assets/steps/11a.svg';\nimport step12Icon from 'assets/stepsnew/12n.svg';\nimport step12IconActive from 'assets/stepsnew/12s.svg';\nimport step12IconPassed from 'assets/stepsnew/12p.svg';\nimport step12IconPActive from 'assets/stepsnew/12ps.svg';\n\nimport heatmapIcon from 'assets/stepsnew/heatmapn.svg';\nimport heatmapIconActive from 'assets/stepsnew/heatmaps.svg';\nimport heatmapIconPassed from 'assets/stepsnew/heatmaps.svg';\nimport heatmapIconPActive from 'assets/stepsnew/heatmaps.svg';\n\nimport tool1Icon from 'assets/tools/1p.svg';\nimport tool1IconActive from 'assets/tools/1a.svg';\nimport tool2Icon from 'assets/tools/2p.svg';\nimport tool2IconActive from 'assets/tools/2a.svg';\n// import tool3Icon from 'assets/tools/3p.svg';\n// import tool3IconActive from 'assets/tools/3a.svg';\nimport tool4Icon from 'assets/tools/4p.svg';\nimport tool4IconActive from 'assets/tools/4a.svg';\nimport tool5Icon from 'assets/tools/5p.svg';\nimport tool5IconActive from 'assets/tools/5a.svg';\nimport tool6Icon from 'assets/tools/6p.svg';\nimport tool6IconActive from 'assets/tools/6a.svg';\nimport tool7Icon from 'assets/tools/7p.svg';\nimport tool7IconActive from 'assets/tools/7a.svg';\nimport tool8Icon from 'assets/tools/8p.svg';\nimport tool8IconActive from 'assets/tools/8a.svg';\nimport tool9Icon from 'assets/tools/9p.svg';\nimport tool9IconActive from 'assets/tools/9a.svg';\nimport tool10Icon from 'assets/tools/10p.svg';\nimport tool10IconActive from 'assets/tools/10p.svg';\n\nimport { IconGenuineCross, IconGenuineFlake, IconGenuineTriangle } from 'shared/components/Icons';\n\nexport const genuieShapes = [\n { icon: IconGenuineTriangle, shape: 2 },\n { icon: IconGenuineCross, shape: 1 },\n { icon: IconGenuineFlake, shape: 3 },\n];\n\nexport const steps = [\n {\n id: 1,\n name: '2D CC/MLO Views',\n icon: step1Icon,\n iconActive: step1IconActive,\n iconPassed: step1IconPassed,\n iconPassedActive: step1IconPActive,\n views: {\n RCC: {\n active: true,\n position: 'right',\n thumbpos: 'left',\n key: 'RCC',\n has_switcher: true,\n dataType: '2D',\n },\n LCC: {\n active: true,\n position: 'left',\n thumbpos: 'right',\n key: 'LCC',\n has_switcher: true,\n dataType: '2D',\n },\n RMLO: {\n active: true,\n position: 'right',\n thumbpos: 'left',\n key: 'RMLO',\n has_switcher: true,\n dataType: '2D',\n },\n LMLO: {\n active: true,\n position: 'left',\n thumbpos: 'right',\n key: 'LMLO',\n has_switcher: true,\n dataType: '2D',\n },\n },\n },\n {\n id: 2,\n name: '2D CC Views',\n icon: step2Icon,\n iconActive: step2IconActive,\n iconPassed: step2IconPassed,\n iconPassedActive: step2IconPActive,\n views: {\n RCC: {\n active: true,\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '2D',\n key: 'RCC',\n },\n LCC: {\n active: true,\n position: 'left',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '2D',\n key: 'LCC',\n },\n },\n },\n {\n id: 3,\n name: '2D MLO Views',\n icon: step3Icon,\n iconActive: step3IconActive,\n iconPassed: step3IconPassed,\n iconPassedActive: step3IconPActive,\n views: {\n RMLO: {\n active: true,\n key: 'RMLO',\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '2D',\n },\n LMLO: {\n active: true,\n key: 'LMLO',\n position: 'left',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '2D',\n },\n },\n },\n {\n id: 4,\n name: '2D/3D RCC Views',\n icon: step4Icon,\n iconActive: step4IconActive,\n iconPassed: step4IconPassed,\n iconPassedActive: step4IconPActive,\n views: {\n RCC: {\n active: true,\n key: 'RCC',\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '2D',\n },\n RCC_L: {\n active: true,\n key: 'RCC',\n layers: true,\n position: 'left',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '3DQ',\n },\n },\n },\n {\n id: 5,\n name: '2D/3D RMLO Views',\n icon: step5Icon,\n iconActive: step5IconActive,\n iconPassed: step5IconPassed,\n iconPassedActive: step5IconPActive,\n views: {\n RMLO: {\n active: true,\n key: 'RMLO',\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '2D',\n },\n RMLO_L: {\n active: true,\n key: 'RMLO',\n layers: true,\n position: 'right',\n thumbpos: 'left',\n has_switcher: true,\n dataType: '3DQ',\n },\n },\n },\n {\n id: 6,\n name: '2D/3D LCC Views',\n icon: step6Icon,\n iconActive: step6IconActive,\n iconPassed: step6IconPassed,\n iconPassedActive: step6IconPActive,\n views: {\n LCC: {\n active: true,\n key: 'LCC',\n position: 'right',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '2D',\n },\n LCC_L: {\n active: true,\n key: 'LCC',\n layers: true,\n position: 'left',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '3DQ',\n },\n },\n },\n {\n id: 7,\n name: '2D/3D LMLO Views',\n icon: step7Icon,\n iconActive: step7IconActive,\n iconPassed: step7IconPassed,\n iconPassedActive: step7IconPActive,\n views: {\n LMLO: {\n active: true,\n key: 'LMLO',\n position: 'right',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '2D',\n },\n LMLO_L: {\n active: true,\n key: 'LMLO',\n layers: true,\n position: 'left',\n thumbpos: 'right',\n has_switcher: true,\n dataType: '3DQ',\n },\n },\n },\n {\n id: 8,\n name: '2D/3D stacked R CC/MLO, Toggle (T) between modes',\n icon: step8Icon,\n iconActive: step8IconActive,\n iconPassed: step8IconPassed,\n iconPassedActive: step8IconPActive,\n views: {\n RCC: {\n active: true,\n key: 'RCC',\n layers: true,\n has_switcher: true,\n dataType: '2D',\n position: 'left',\n thumbpos: 'left',\n },\n RMLO: {\n active: true,\n key: 'RMLO',\n layers: true,\n has_switcher: true,\n dataType: '2D',\n position: 'left',\n thumbpos: 'left',\n },\n },\n },\n {\n id: 9,\n name: '2D/3D stacked L CC/MLO, Toggle (T) between modes',\n icon: step9Icon,\n iconActive: step9IconActive,\n iconPassed: step9IconPassed,\n iconPassedActive: step9IconPActive,\n views: {\n LMLO: {\n active: true,\n key: 'LMLO',\n layers: true,\n has_switcher: true,\n dataType: '2D',\n position: 'right',\n thumbpos: 'right',\n },\n LCC: {\n active: true,\n key: 'LCC',\n layers: true,\n has_switcher: true,\n dataType: '2D',\n position: 'left',\n thumbpos: 'right',\n },\n },\n },\n {\n id: 11,\n name: 'question',\n icon: step11Icon,\n iconActive: step11IconActive,\n iconPassed: step11IconPassed,\n iconPassedActive: step11IconPActive,\n type: 'modal',\n views: {},\n disabled_no_active: 9,\n },\n {\n id: 12,\n name: 'heatmap',\n icon: heatmapIcon,\n iconActive: heatmapIconActive,\n iconPassed: heatmapIconPassed,\n iconPassedActive: heatmapIconPActive,\n type: 'modal',\n views: {},\n disabled_no_active: 10,\n },\n {\n id: 13,\n name: 'explanator',\n icon: step12Icon,\n iconActive: step12IconActive,\n iconPassed: step12IconPassed,\n iconPassedActive: step12IconPActive,\n type: 'modal',\n views: {},\n disabled_no_active: 10,\n },\n];\n\nexport const tools = [\n {\n name: 'FT',\n title: 'Show/Hide Finding Tool',\n icon: tool1Icon,\n iconActive: tool1IconActive,\n type: 'findingToggle',\n cursor: 'default',\n height: 23,\n onTimeAction: true,\n canStayActive: true,\n noResetFindings: true,\n },\n {\n name: 'F',\n title: 'Activate Finding Tool',\n icon: tool2Icon,\n iconActive: tool2IconActive,\n type: 'finding',\n cursor: 'arrow',\n height: 23,\n onTimeAction: false,\n },\n {\n name: 'FR',\n title: 'Reset Finding Tool',\n icon: tool10Icon,\n iconActive: tool10IconActive,\n type: 'findingReset',\n cursor: 'default',\n height: 23,\n onTimeAction: true,\n },\n {\n type: 'separator',\n },\n {\n name: 'R',\n title: 'Reset Viewers',\n icon: tool4Icon,\n iconActive: tool4IconActive,\n type: 'reset',\n cursor: 'default',\n onTimeAction: true,\n },\n {\n name: 'P',\n title: 'Activate Panning Tool',\n icon: tool5Icon,\n iconActive: tool5IconActive,\n type: 'pan',\n cursor: 'pan',\n onTimeAction: false,\n },\n {\n name: 'I Z',\n title: 'Activate Interactive Zoom',\n icon: tool6Icon,\n iconActive: tool6IconActive,\n type: 'zoomInteractive',\n cursor: 'izoom',\n onTimeAction: false,\n },\n {\n name: 'Z 1:1',\n title: 'Activate 1:1 Tool',\n icon: tool7Icon,\n iconActive: tool7IconActive,\n type: 'zoom1_1',\n cursor: 'fullzoom',\n onTimeAction: false,\n },\n {\n name: 'M',\n title: 'Activate Magnifier Tool',\n icon: tool8Icon,\n iconActive: tool8IconActive,\n type: 'zoomMagnifier',\n cursor: 'mzoom',\n onTimeAction: false,\n },\n {\n name: 'W/L',\n title:\n 'Activate Window/Level Tool (W; for presets move mouse over viewer and press numeric keys 1, 2, 3, ...)',\n icon: tool9Icon,\n iconActive: tool9IconActive,\n type: 'windowLevel',\n cursor: 'wlevel',\n onTimeAction: false,\n },\n];\n\nexport const getToolByType = type => {\n return tools.find(item => item.type === type);\n};\n\nexport const getDefaultType = (type, datas) => {\n if (type === '3DQ' && !datas['3DQ'] && datas['tomo']) return 'tomo';\n return type;\n};\n","import { LESSON_STATUSES } from 'configs/constants';\n\n/* eslint-disable no-console */\nexport const zoomImageMouseClick = (cornerstone, element, bool, scale, e) => {\n const step = 0.05;\n const viewport = cornerstone.getViewport(element);\n viewport.scale -= bool ? -step : step;\n if (scale) viewport.scale = scale;\n cornerstone.setViewport(element, viewport);\n return false;\n};\n\nexport const getDefaultImageScale = (element, image) => {\n const { clientWidth, clientHeight } = element;\n const { width, height } = image;\n if (width > height) return width > clientWidth ? clientWidth / width : clientHeight / height;\n else return clientHeight / height;\n};\n\nexport const maximazeElement = (cornerstone, element, toggle) => {\n element.classList[toggle ? 'toggle' : 'add']('maximize');\n cornerstone.fitToWindow(element);\n cornerstone.resize(element);\n};\n\nconst getCompletedSteps = (episodes, id) => {\n let body = episodes.filter(item => item.caseId === id);\n body = body.sort((a, b) => a.orderNum - b.orderNum);\n const result = [];\n body.forEach((item, index) => {\n if (item.status === LESSON_STATUSES.inProgress || item.status === LESSON_STATUSES.completed)\n result.push(index);\n });\n return result;\n};\n\nconst setFindings = (data, userLessonId) => {\n try {\n if (data && data.length) {\n data = data.map(f => {\n f.vectorData = JSON.parse(f.vectorData);\n f.vectorData = f.vectorData.map(i => ({ ...i, x: i.pointX, y: i.pointY }));\n return f;\n });\n return data.filter(item => userLessonId === item.userLessonId)[0];\n } else return null;\n } catch (err) {\n return null;\n }\n};\n\nconst checkDicomNameOnViews = (name, newTpe, views) => {\n if (!views[name]) return name;\n const { dicomImageType: currentType } = views[name];\n if (currentType === 'i2d') return false;\n if (currentType === 'g2d' && newTpe === 'r2d') return false;\n return name;\n};\n\nconst getDicomDataName = dicomItem => {\n const { imageLaterality, viewPosition, dicomImageType } = dicomItem;\n const types = { '3dq': '_3DQ', i2d: '_2D', g2d: '_2D', r2d: '_2D', tomo: '_TOMO', sub: '_TOMO' };\n const type = types[dicomImageType];\n return `${imageLaterality}${viewPosition}${type}`;\n};\n\nexport const generateViewerData = (data, lesson) => {\n const result = [];\n const views = {};\n data.caseDicomItems.forEach(({ dicomItem }, index) => {\n const tempName = getDicomDataName(dicomItem);\n const name = checkDicomNameOnViews(tempName, dicomItem.dicomImageType, views);\n if (!name) return;\n views[name] = {\n name,\n position: name[0] === 'R' ? 'right' : 'left',\n protocolName: dicomItem.protocolName,\n imageLaterality: dicomItem.imageLaterality,\n viewPosition: dicomItem.viewPosition,\n dicomImageType: dicomItem.dicomImageType,\n id: dicomItem.id,\n patient: {\n name: dicomItem.patientName,\n age: dicomItem.patientAge,\n sex: dicomItem.patientSex,\n id: dicomItem.patientId,\n birthDate: dicomItem.patientBirthDate,\n },\n images: dicomItem.dicomItemImages.map(img => {\n const emptyFinding = {\n caseId: data.id,\n dicomId: dicomItem.id,\n imageId: img.id,\n userLessonId: lesson.userLessonId,\n };\n return {\n id: img.id,\n url: img.lutUrl || img.url,\n finding: setFindings(img.vectorDatas, lesson.userLessonId),\n emptyFinding,\n geniusAIDataList: img.geniusAIDataList,\n preSlices: img.preSlices,\n postSlices: img.postSlices,\n };\n }),\n };\n });\n const completed_steps = getCompletedSteps(lesson.episodes, data.id);\n result.push({ id: data.id, name: data.title, completed_steps, views });\n return result;\n};\n\n(function(target) {\n if (!target || !target.prototype) return;\n target.prototype.arrow = function(startX, startY, endX, endY, controlPoints) {\n var dx = endX - startX;\n var dy = endY - startY;\n var len = Math.sqrt(dx * dx + dy * dy);\n var sin = dy / len;\n var cos = dx / len;\n var a = [];\n a.push(0, 0);\n for (var i = 0; i < controlPoints.length; i += 2) {\n var x1 = controlPoints[i];\n var y1 = controlPoints[i + 1];\n a.push(x1 < 0 ? len + x1 : x1, y1);\n }\n a.push(len, 0);\n for (var j = controlPoints.length; j > 0; j -= 2) {\n var x2 = controlPoints[j - 2];\n var y2 = controlPoints[j - 1];\n a.push(x2 < 0 ? len + x2 : x2, -y2);\n }\n a.push(0, 0);\n for (var k = 0; k < a.length; k += 2) {\n var x3 = a[k] * cos - a[k + 1] * sin + startX;\n var y3 = a[k] * sin + a[k + 1] * cos + startY;\n if (i === 0) this.moveTo(x3, y3);\n else this.lineTo(x3, y3);\n }\n };\n})(CanvasRenderingContext2D);\n\nexport const drawArrow = (c, viewport, e) => {\n c.lineWidth = 1;\n c.strokeStyle = '#00b5ec';\n c.fillStyle = '#00b5ec';\n c.setTransform(1, 0, 0, 1, 10, 0);\n\n c.translate(0, 0);\n c.beginPath();\n c.arrow(50, 50, 0, 0, [0, 2, -10, 2, -10, 5]);\n\n c.fill();\n};\n\nexport const getFindingsCoordinates = points => {\n if (!points || !points.data) return null;\n try {\n const result = points.data.map(i => {\n const item = i.handles.end;\n return { pointX: item.x, pointY: item.y };\n });\n return JSON.stringify(result);\n } catch (err) {\n console.log(err);\n return null;\n }\n};\n\nexport const createFindingEventData = ({ x, y }) => {\n return {\n visible: true,\n active: true,\n color: undefined,\n invalidated: true,\n handles: {\n end: {\n x,\n y,\n highlight: true,\n active: true,\n },\n },\n };\n};\n\nexport const createGenuieEventData = ({ x, y, shape, percentage }) => {\n return {\n visible: true,\n active: true,\n color: undefined,\n invalidated: true,\n handles: {\n end: {\n x,\n y,\n percentage: percentage,\n shape: shape,\n highlight: true,\n active: true,\n },\n },\n };\n};\n\nexport const getDicomImagesObj = images => {\n const result = {};\n images.forEach(item => {\n result[item.url] = item;\n });\n return result;\n};\n\nexport const sumShapes = (images, shape) => {\n let sum = 0;\n images.forEach(item => {\n if (item.geniusAIDataList && item.geniusAIDataList.length) {\n const shapes = item.geniusAIDataList.filter(i => Number(i.shape) === Number(shape));\n sum += shapes ? shapes.length : 0;\n }\n });\n return sum;\n};\n","import React from 'react';\n\nconst getSmallerSize = imageWidth => {\n return Number((imageWidth / 50).toFixed());\n};\n\nconst Thumbnail = ({ viewport }) => {\n if (!viewport || !viewport.image) return null;\n const { scale, initialScale, translation, rotation, vflip, hflip } = viewport;\n const smallNum = getSmallerSize(viewport.image.width);\n const width = viewport.image.width / smallNum;\n const height = viewport.image.height / smallNum;\n const coreSize = {\n width: (initialScale * width) / scale,\n height: (initialScale * height) / scale,\n };\n const corePos = {\n transform: `translate(${-(translation.x / smallNum)}px, ${-(translation.y / smallNum)}px)`,\n };\n const imageStyle = {\n backgroundImage: `url('${viewport.image.imageId}')`,\n transform: `rotate(${rotation}deg) rotateX(${vflip ? 180 : 0}deg) rotateY(${\n hflip ? 180 : 0\n }deg)`,\n };\n if (coreSize.width > 100 || initialScale >= scale) return null;\n return (\n \n );\n};\n\nexport default Thumbnail;\n","import React, { useState } from 'react';\nimport ReactModal from 'react-modal';\nimport { IconClose } from 'shared/components/Icons';\n\nexport const getPrePostSliceItems = (index, pre, post) => {\n let prePostSlicesIndexes = [];\n if (post) {\n for (let i = index + 1; i <= index + post; i += 1) {\n prePostSlicesIndexes.push(i);\n }\n }\n if (pre) {\n for (let i = index - 1; i >= index - pre; i -= 1) {\n prePostSlicesIndexes.push(i);\n }\n }\n return { prePostSlicesIndexes };\n};\n\nexport const getImagesPrePostSlices = images => {\n let result = new Set();\n images.forEach((img, index) => {\n const hasGeniusShapes = !!img.geniusAIDataList?.length;\n if (!hasGeniusShapes) return;\n const { prePostSlicesIndexes } = getPrePostSliceItems(index, img.preSlices, img.postSlices);\n result = new Set([...result, ...prePostSlicesIndexes]);\n });\n return [...result];\n};\n\nexport const getPrimarySlices = images => {\n const result = new Set();\n images.forEach((img, index) => {\n const hasGeniusShapes = !!img.geniusAIDataList?.length;\n if (hasGeniusShapes || (hasGeniusShapes && (!!img.preSlices || !!img.postSlices))) {\n result.add(index);\n }\n });\n return [...result];\n};\n\nexport const getImagesSortedSlices = images => {\n const all = [];\n images.forEach((img, index) => {\n const hasGeniusShapes = !!img.geniusAIDataList?.length;\n if (!hasGeniusShapes) return;\n const { prePostSlicesIndexes } = getPrePostSliceItems(index, img.preSlices, img.postSlices);\n all.push([...new Set([index, ...prePostSlicesIndexes])]);\n });\n return all;\n};\n\nconst PrePostSlices = ({ onSaveSlice, activeImage, imagesCount, playing }) => {\n const [activeTab, setActiveTab] = useState('preSlices');\n const [modalState, setModalState] = useState(false);\n const [imageData, setImageData] = useState();\n\n const onOpenModal = () => {\n setImageData({ index: activeImage.index, image: { ...activeImage } });\n setModalState(true);\n };\n\n const onSliceChange = ({ e }) => {\n const tempData = { ...imageData };\n imageData.image[activeTab] = e.target.value;\n setImageData(tempData);\n };\n\n const onChangeSliceNumber = increase => {\n const tempData = { ...imageData };\n imageData.image[activeTab] += increase ? 1 : -1;\n setImageData(tempData);\n };\n\n const onSaveHandle = () => {\n setModalState(false);\n onSaveSlice({ ...imageData });\n };\n\n const currentValue = imageData ? imageData.image[activeTab] : 0;\n\n const minuseButtonDisabled = currentValue === 0;\n const addButtonDisabled =\n (activeTab === 'postSlices' && imageData?.index + currentValue >= imagesCount) ||\n (activeTab === 'preSlices' && imageData?.index - currentValue <= 0);\n\n const getChosenSlices = () => {\n const { prePostSlicesIndexes } = getPrePostSliceItems(\n imageData.index,\n imageData.image.preSlices,\n imageData.image.postSlices,\n );\n const slices = [...prePostSlicesIndexes];\n return slices.join(',');\n };\n\n const hasGeniusShapes = !!activeImage?.geniusAIDataList?.length;\n\n return (\n \n {!playing && (\n
\n Add Pre/Post Slices +\n \n )}\n {imageData && (\n
\n \n
\n
\n
Add Pre/Post Slices \n \n
setModalState(false)} className='btn p-0 modal-close-btn'>\n \n \n
\n
\n Please, indicate how many pre-slices you want to mark for{' '}\n Slice {imageData.index + 1} .\n
\n
\n
\n setActiveTab('preSlices')}\n className={`btn slice-tab ${activeTab === 'preSlices' ? 'active' : ''}`}\n >\n Pre Slice\n \n setActiveTab('postSlices')}\n className={`btn slice-tab ml-3 ${activeTab === 'postSlices' ? 'active' : ''}`}\n >\n Post Slice\n \n
\n
\n
onChangeSliceNumber(false)}\n className='btn btn-slice-change'\n >\n -\n \n
\n \n
\n
onChangeSliceNumber(true)}\n className='btn btn-slice-change'\n >\n +\n \n
\n
\n Chosen Slices: {getChosenSlices()} \n
\n
\n
\n \n Save\n \n
\n
\n \n )}\n
\n );\n};\n\nexport default PrePostSlices;\n","/* eslint-disable no-console */\nimport React, { useState, useEffect, useRef } from 'react';\nimport Slider from 'react-rangeslider';\nimport 'react-rangeslider/umd/rangeslider.min.css';\nimport { IconAiPlay, IconPlayFill, IconSliceNext, IconSlicePrev } from 'shared/components/Icons';\nimport Loading from 'shared/components/Loading';\nimport pause from 'assets/pause.svg';\nimport speedIcon from 'assets/speed.svg';\nimport { getImagesPrePostSlices, getImagesSortedSlices, getPrimarySlices } from './PrePostSlices';\n\nconst stackWords = {\n CC: { top: 'H', bottom: 'F' },\n MLO: { top: 'M', bottom: 'L' },\n};\n\nconst speeds = [\n { name: 'Low', value: 160 },\n { name: 'Medium', value: 80 },\n { name: 'High', value: 40 },\n];\n\nconst modes = [\n { name: 'Genius AI Mode', value: 'genius' },\n { name: 'Normal Mode', value: 'normal' },\n];\n\nconst StackControl = ({\n cornerstoneTools,\n element,\n cornerstone,\n imageType,\n setActiveTool,\n className,\n images,\n play,\n setPlay,\n isGenuine,\n}) => {\n const stackEl = useRef();\n const [speed, setSpeed] = useState(80);\n const [mode, setMode] = useState('normal');\n const [fetch, setFetch] = useState(false);\n const isAIMode = mode === 'genius';\n\n const playIndex = useRef({ index: 0, increase: true, images: undefined });\n const scale = useRef(0);\n const playInterval = useRef();\n\n const primarySlices = isAIMode ? getPrimarySlices(images) : [];\n const allPrePostSlices = isAIMode ? getImagesPrePostSlices(images) : [];\n const allSortedSlices = isAIMode ? getImagesSortedSlices(images) : [];\n const hasNextSlice = primarySlices.indexOf(playIndex.current.index) < primarySlices.length - 1;\n const hasPrevSlice = primarySlices.indexOf(playIndex.current.index) !== 0;\n\n const getStackData = () => {\n const stackData = cornerstoneTools.getToolState(element, 'stack');\n const { currentImageIdIndex, imageIds } = stackData.data[0];\n return { currentImageIdIndex, imageIds };\n };\n\n const { imageIds } = getStackData();\n\n const changeHandle = index => {\n if (fetch || play) return;\n if (!playIndex.current.images) {\n loadAllImages();\n return;\n }\n playIndex.current.index = index;\n scale.current = index;\n changeStackImage(playIndex.current.images, index);\n };\n\n const changeStackImage = (images, index) => {\n if (images[index] !== undefined) {\n const viewport = cornerstone.getViewport(element);\n const stackData = cornerstoneTools.getToolState(element, 'stack');\n stackData.data[0].currentImageIdIndex = Number(index);\n cornerstone.displayImage(element, images[index], viewport);\n }\n };\n\n const playStack = images => {\n try {\n let { index, increase } = playIndex.current;\n index += increase ? 1 : -1;\n if (!imageIds[index]) {\n increase = !increase;\n index += increase ? 2 : -2;\n }\n changeStackImage(images, index);\n playIndex.current.index = index;\n playIndex.current.increase = increase;\n } catch (err) {\n console.log(err);\n }\n };\n\n const playStackSlices = images => {\n try {\n let { index, increase } = playIndex.current;\n const activeSlice = allSortedSlices.findIndex(i => i.includes(index));\n const nearPrimary = activeSlice !== -1 ? allSortedSlices[activeSlice] : allSortedSlices[0];\n const sliceImages = [...(nearPrimary || [])].sort((a, b) => a - b);\n const currentIndex = sliceImages.indexOf(index);\n index = sliceImages[currentIndex === -1 ? 0 : currentIndex + (increase ? 1 : -1)];\n if (!imageIds[index]) {\n increase = !increase;\n index = sliceImages[currentIndex === -1 ? 0 : currentIndex + (increase ? 1 : -1)];\n }\n changeStackImage(images, index);\n playIndex.current.index = index;\n playIndex.current.increase = increase;\n } catch (err) {\n console.log(err);\n }\n };\n\n const setPlayInterval = (speed, changedToAIMode) => {\n if (changedToAIMode || isAIMode) {\n playInterval.current = setInterval(playStackSlices, speed, playIndex.current.images);\n } else {\n playInterval.current = setInterval(playStack, speed, playIndex.current.images);\n }\n };\n\n const loadAllImages = async play => {\n setFetch(true);\n if (!playIndex.current.images) {\n const { imageIds } = getStackData();\n const imageLoads = imageIds.map(async item => await cornerstone.loadAndCacheImage(item));\n const images = await Promise.all(imageLoads);\n playIndex.current.images = images;\n }\n setFetch(false);\n if (play) setPlayInterval(speed);\n };\n\n const onElementScroll = e => {\n e.preventDefault();\n\n if (!playIndex.current.images) {\n if (fetch || play) return;\n loadAllImages();\n return;\n }\n\n const { imageIds } = getStackData();\n scale.current += e.deltaY * -0.01;\n scale.current = Math.min(Math.max(0, scale.current), imageIds.length);\n\n if (playIndex.current.index !== Math.floor(scale.current)) {\n changeHandle(Math.floor(scale.current));\n }\n };\n\n const onChangeStart = () => {\n // setActiveTool({ type: 'remove' });\n };\n\n const handleUserKeyPress = event => {\n const { keyCode } = event;\n if (keyCode === 32)\n setPlay(p => {\n return !p;\n });\n };\n\n const stopPlayerUnmount = () => {\n window.removeEventListener('keydown', handleUserKeyPress);\n setPlay(false);\n clearInterval(playInterval.current);\n playIndex.current = { index: 0, increase: true, images: undefined };\n };\n\n const onSpeedChange = async value => {\n setSpeed(value);\n if (play) {\n clearInterval(playInterval.current);\n setPlayInterval(value);\n }\n };\n\n const onPlayerModeChanges = value => {\n setMode(value);\n if (play) {\n clearInterval(playInterval.current);\n setPlayInterval(speed, value === 'genius');\n }\n };\n\n const renderLines = length => {\n const lines = [];\n for (let index = length - 1; index >= 0; index--) {\n const hasGenuie = images?.[index]?.geniusAIDataList?.length > 0;\n const hasPrimarySlice =\n hasGenuie || (hasGenuie && (!!images?.[index]?.postSlices || images?.[index]?.preSlices));\n const hasPrePostSlice = !hasPrimarySlice && allPrePostSlices.includes(index);\n lines.push(\n ,\n );\n }\n return lines;\n };\n\n const onMovePrimarySlice = isNext => {\n setPlay(false);\n const currentIndex = primarySlices.indexOf(playIndex.current.index);\n const newIndex = currentIndex === -1 ? 0 : currentIndex + (isNext ? 1 : -1);\n changeHandle(primarySlices[newIndex]);\n };\n\n useEffect(() => {\n playIndex.current = { index: 0, increase: true, images: undefined };\n scale.current = 0;\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n if (play) {\n element.onwheel = null;\n loadAllImages(true);\n } else {\n scale.current = playIndex.current.index;\n element.onwheel = onElementScroll;\n clearInterval(playInterval.current);\n }\n //eslint-disable-next-line\n }, [play]);\n\n useEffect(() => {\n window.addEventListener('keydown', handleUserKeyPress);\n return () => {\n stopPlayerUnmount();\n };\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n setTimeout(() => {\n images.map(item => cornerstone.loadAndCacheImage(item.url));\n }, 100);\n stopPlayerUnmount();\n //eslint-disable-next-line\n }, []);\n\n return (\n <>\n \n
\n {renderLines(imageIds.length)}\n
\n
\n
\n
\n {speeds.map(item => (\n onSpeedChange(item.value)}\n >\n {item.name}\n \n ))}\n
\n
\n
\n
{\n setPlay(p => !p);\n e.preventDefault();\n e.stopPropagation();\n }}\n >\n {fetch ? (\n \n ) : play ? (\n \n ) : (\n \n )}\n \n {!play && isGenuine && (\n
\n {modes.map(item => (\n onPlayerModeChanges(item.value)}\n >\n {item.name}\n \n ))}\n
\n )}\n
\n
{stackWords[imageType].top}
\n
\n
{stackWords[imageType].bottom}
\n
\n {Number(getStackData().currentImageIdIndex) + 1}/{imageIds.length}\n
\n {isAIMode && (\n
\n \n \n \n \n \n \n
\n )}\n
Buffering in Process
\n
\n {play && isAIMode && (\n \n Genius AI Mode \n
\n )}\n >\n );\n};\n\nexport default StackControl;\n","/**\n * Triggers a CustomEvent.\n * @public\n * @method triggerEvent\n *\n * @param {EventTarget} el The element or EventTarget to trigger the event upon.\n * @param {String} type The event type name.\n * @param {Object|null} [detail=null] The event data to be sent.\n * @returns {Boolean} The return value is false if at least one event listener called preventDefault(). Otherwise it returns true.\n */\nexport default function triggerEvent(el, type, detail = null) {\n let event;\n\n // This check is needed to polyfill CustomEvent on IE11-\n if (typeof window.CustomEvent === 'function') {\n event = new CustomEvent(type, {\n detail,\n cancelable: true,\n });\n } else {\n event = document.createEvent('CustomEvent');\n event.initCustomEvent(type, true, true, detail);\n }\n\n return el.dispatchEvent(event);\n}\n","/* eslint-disable consistent-return */\nimport * as cornerstone from 'cornerstone-core';\nimport uuidv4 from './../util/uuidv4.js';\nimport triggerEvent from './../util/triggerEvent.js';\n\n/**\n * Implements an imageId specific tool state management strategy. This means that\n * Measurements data is tied to a specific imageId and only visible for enabled elements\n * That are displaying that imageId.\n * @public\n * @constructor newImageIdSpecificToolStateManager\n * @memberof StateManagement\n *\n * @returns {Object} An imageIdSpecificToolStateManager instance.\n */\nfunction newImageIdSpecificToolStateManager() {\n let toolState = {};\n\n // Here we add tool state, this is done by tools as well\n // As modules that restore saved state\n\n function saveImageIdToolState(imageId) {\n return toolState[imageId];\n }\n\n function restoreImageIdToolState(imageId, imageIdToolState) {\n toolState[imageId] = imageIdToolState;\n }\n\n function saveToolState() {\n return toolState;\n }\n\n function restoreToolState(savedToolState) {\n toolState = savedToolState;\n }\n\n // Here we add tool state, this is done by tools as well\n // As modules that restore saved state\n function addElementToolState(element, toolType, data) {\n const enabledElement = cornerstone.getEnabledElement(element);\n\n // If we don't have an image for this element exit early\n if (!enabledElement.image) {\n return;\n }\n addImageIdToolState(enabledElement.image.imageId, toolType, data);\n }\n\n function addImageIdToolState(imageId, toolType, data) {\n // If we don't have any tool state for this imageId, add an empty object\n if (toolState.hasOwnProperty(imageId) === false) {\n toolState[imageId] = {};\n }\n\n const imageIdToolState = toolState[imageId];\n\n // If we don't have tool state for this type of tool, add an empty object\n if (imageIdToolState.hasOwnProperty(toolType) === false) {\n imageIdToolState[toolType] = {\n data: [],\n };\n }\n\n const toolData = imageIdToolState[toolType];\n\n // Finally, add this new tool to the state\n toolData.data.push(data);\n }\n\n function getElementToolState(element, toolType) {\n const enabledElement = cornerstone.getEnabledElement(element);\n\n // If the element does not have an image return undefined.\n if (!enabledElement.image) {\n return;\n }\n\n return getImageIdToolState(enabledElement.image.imageId, toolType);\n }\n\n // Here you can get state - used by tools as well as modules\n // That save state persistently\n function getImageIdToolState(imageId, toolType) {\n // If we don't have any tool state for this imageId, return undefined\n if (toolState.hasOwnProperty(imageId) === false) {\n return;\n }\n\n const imageIdToolState = toolState[imageId];\n\n // If we don't have tool state for this type of tool, return undefined\n if (imageIdToolState.hasOwnProperty(toolType) === false) {\n return;\n }\n\n return imageIdToolState[toolType];\n }\n\n // Clears all tool data from this toolStateManager.\n function clearElementToolState(element) {\n const enabledElement = cornerstone.getEnabledElement(element);\n\n if (!enabledElement.image) {\n return;\n }\n clearImageIdToolState(enabledElement.image.imageId);\n }\n\n function clearImageIdToolState(imageId) {\n if (toolState.hasOwnProperty(imageId) === false) {\n return;\n }\n\n delete toolState[imageId];\n }\n\n return {\n get: getElementToolState,\n add: addElementToolState,\n clear: clearElementToolState,\n getImageIdToolState,\n addImageIdToolState,\n clearImageIdToolState,\n saveImageIdToolState,\n restoreImageIdToolState,\n saveToolState,\n restoreToolState,\n toolState,\n };\n}\n\n// A global imageIdSpecificToolStateManager - the most common case is to share state between all\n// Visible enabled images\nconst globalImageIdSpecificToolStateManager = newImageIdSpecificToolStateManager();\n\n/**\n * Returns the toolstate for a specific element.\n * @public\n * @function getElementToolStateManager\n *\n * @param {HTMLElement} element The element.\n * @returns {Object} The toolState.\n */\nfunction getElementToolStateManager(element) {\n const enabledElement = cornerstone.getEnabledElement(element);\n // If the enabledElement has no toolStateManager, create a default one for it\n // NOTE: This makes state management element specific\n\n if (enabledElement.toolStateManager === undefined) {\n enabledElement.toolStateManager = globalImageIdSpecificToolStateManager;\n }\n\n return enabledElement.toolStateManager;\n}\n\n/**\n * Adds tool state to the toolStateManager, this is done by tools as well\n * as modules that restore saved state.\n * @public\n * @method addToolState\n *\n * @param {HTMLElement} element The element.\n * @param {string} toolType The toolType of the state.\n * @param {Object} measurementData The data to store in the state.\n * @returns {undefined}\n */\nfunction addToolState(element, toolType, measurementData) {\n const toolStateManager = getElementToolStateManager(element);\n\n measurementData.uuid = measurementData.uuid || uuidv4();\n toolStateManager.add(element, toolType, measurementData);\n\n const eventType = 'cornerstonetoolsmeasurementremoved';\n const eventData = {\n toolType,\n element,\n measurementData,\n };\n\n triggerEvent(element, eventType, eventData);\n}\n\n/**\n * Returns tool specific state of an element. Used by tools as well as modules\n * that save state persistently\n * @export\n * @public\n * @method\n * @name getToolState\n *\n * @param {HTMLElement} element The element.\n * @param {string} toolType The toolType of the state.\n * @returns {Object} The element's state for the given toolType.\n */\nfunction getToolState(element, toolType) {\n const toolStateManager = getElementToolStateManager(element);\n\n return toolStateManager.get(element, toolType);\n}\n\n/**\n * Removes specific tool state from the toolStateManager.\n * @public\n * @method removeToolState\n *\n * @param {HTMLElement} element The element.\n * @param {string} toolType The toolType of the state.\n * @param {Object} data The data to remove from the toolStateManager.\n * @returns {undefined}\n */\nfunction removeToolState(element, toolType, data) {\n const toolStateManager = getElementToolStateManager(element);\n const toolData = toolStateManager.get(element, toolType);\n\n if (!toolData || !toolData.data || !toolData.data.length) {\n return;\n }\n\n // Find this tool data\n let indexOfData = -1;\n\n for (let i = 0; i < toolData.data.length; i++) {\n if (toolData.data[i] === data) {\n indexOfData = i;\n }\n }\n\n if (indexOfData !== -1) {\n toolData.data.splice(indexOfData, 1);\n\n const eventType = 'cornerstonetoolsmeasurementremoved';\n const eventData = {\n toolType,\n element,\n measurementData: data,\n };\n\n triggerEvent(element, eventType, eventData);\n }\n}\n\nexport { getToolState, addToolState, removeToolState };\n","let defaultColor = 'white';\nlet activeColor = 'greenyellow';\nlet fillColor = 'transparent';\n\nfunction setFillColor(color) {\n fillColor = color;\n}\n\nfunction getFillColor() {\n return fillColor;\n}\n\nfunction setToolColor(color) {\n defaultColor = color;\n}\n\nfunction getToolColor() {\n return defaultColor;\n}\n\nfunction setActiveColor(color) {\n activeColor = color;\n}\n\nfunction getActiveColor() {\n return activeColor;\n}\n\nfunction getColorIfActive(data) {\n if (data.color) {\n return data.color;\n }\n\n return data.active ? activeColor : defaultColor;\n}\n\nconst toolColors = {\n setFillColor,\n getFillColor,\n setToolColor,\n getToolColor,\n setActiveColor,\n getActiveColor,\n getColorIfActive,\n};\n\nexport default toolColors;\n","/* eslint-disable import/no-anonymous-default-export */\n/**\n * This function manages the {@link https://www.w3.org/TR/2dcontext/#the-canvas-state|save/restore}\n * pattern for working in a new context state stack. The parameter `fn` is passed the `context` and can\n * execute any API calls in a clean stack.\n * @public\n * @method draw\n * @memberof Drawing\n *\n * @param {CanvasRenderingContext2D} context - Target Canvas\n * @param {ContextFn} fn - A function which performs drawing operations within the given context.\n * @returns {undefined}\n */\nexport default function(context, fn) {\n context.save();\n fn(context);\n context.restore();\n}\n","let defaultWidth = 1;\nlet activeWidth = 2;\n\nfunction setToolWidth(width) {\n defaultWidth = width;\n}\n\nfunction getToolWidth() {\n return defaultWidth;\n}\n\nfunction setActiveWidth(width) {\n activeWidth = width;\n}\n\nfunction getActiveWidth() {\n return activeWidth;\n}\n\nconst toolStyle = {\n setToolWidth,\n getToolWidth,\n setActiveWidth,\n getActiveWidth,\n};\n\nexport default toolStyle;\n","/* eslint-disable import/no-anonymous-default-export */\nimport toolStyle from './../stateManagement/toolStyle.js';\n\n/**\n * This function manages the beginPath/stroke pattern for working with\n * {@link https://www.w3.org/TR/2dcontext/#drawing-paths-to-the-canvas|path objects}.\n *\n * @public\n * @function path\n * @memberof Drawing\n *\n * @param {CanvasRenderingContext2D} context - Context to add path to\n * @param {Object} [options={}] - Drawing Options\n * @param {StrokeStyle} [options.color] - The stroke style of the path.\n * @param {number} [options.lineWidth] - The width of lines in the path. If null, no line width is set.\n * If undefined then toolStyle.getToolWidth() is set.\n * @param {FillStyle} [options.fillStyle] - The style to fill the path with. If undefined then no filling is done.\n * @param {Number[]} [options.lineDash] - The {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash|dash pattern}\n * to use on the lines.\n * @param {boolean} [options.shouldDrawLines = true] Whether context.stroke should be evoked.\n * @param {ContextFn} fn - A drawing function to execute with the provided stroke pattern.\n * @returns {undefined}\n */\nexport default function(context, options = {}, fn) {\n const { color, lineWidth, fillStyle, lineDash, shouldDrawLines = true } = options;\n\n context.beginPath();\n context.strokeStyle = color || context.strokeStyle;\n\n context.lineWidth =\n lineWidth || (lineWidth === undefined && toolStyle.getToolWidth()) || context.lineWidth;\n if (lineDash) {\n context.setLineDash(lineDash);\n }\n\n fn(context);\n\n if (fillStyle) {\n context.fillStyle = fillStyle;\n context.fill();\n }\n\n if (shouldDrawLines) {\n context.stroke();\n }\n\n if (lineDash) {\n context.setLineDash([]);\n }\n}\n","import * as cornerstone from 'cornerstone-core';\nimport toolStyle from './../stateManagement/toolStyle.js';\nimport toolColors from './../stateManagement/toolColors.js';\nimport path from './path.js';\n\nconst drawArrow = (c, { x, y }) => {\n c.setTransform(1, 0, 0, 1, 10, 0);\n c.translate(x - 8, y + 4);\n c.arrow(30, 20, 0, 0, [0, 2, -10, 2, -10, 5]);\n};\n\nconst drawFlake = (c, { x, y }, percent) => {\n x -= 12;\n y -= 12;\n\n if (percent) {\n c.font = '16px Roboto';\n c.fillStyle = '#EDD500';\n c.fillText(`${percent}%`, x, y - 5);\n }\n\n c.setTransform(1.33333, 0, 0, 1.33333, x, y);\n c.setTransform(1, 0, 0, 1, x, y);\n c.setTransform(1, 0, 0, 1, x + 8.5, y + 8.475);\n c.setTransform(1, 0, 0, 1, x + 10.025, y);\n c.beginPath();\n c.moveTo(0, 0);\n c.lineTo(4, 0);\n c.lineTo(4, 24);\n c.lineTo(0, 24);\n c.closePath();\n c.fillStyle = '#EDD500';\n c.fill('nonzero');\n c.setTransform(1, 0, 0, 1, x + 8.5, y + 6.719);\n c.beginPath();\n c.moveTo(13.57, -2.5);\n c.lineTo(15.551, 1.108);\n c.lineTo(-6.519, 14.477);\n c.lineTo(-8.5, 10.869);\n c.lineTo(13.57, -2.5);\n c.closePath();\n c.fill('nonzero');\n c.beginPath();\n c.moveTo(-6.519, -2.5);\n c.lineTo(-8.5, 1.108);\n c.lineTo(13.57, 14.408);\n c.lineTo(15.551, 10.8);\n c.lineTo(-6.519, -2.5);\n c.closePath();\n c.fill('nonzero');\n};\n\nconst drawCross = (c, { x, y }, percent) => {\n x -= 12;\n y -= 12;\n\n if (percent) {\n c.font = '16px Roboto';\n c.fillStyle = '#EDD500';\n c.fillText(`${percent}%`, x, y - 5);\n }\n\n c.setTransform(1.33333, 0, 0, 1.33333, x, y);\n c.setTransform(1, 0, 0, 1, x, y);\n c.setTransform(1, 0, 0, 1, x + 5.9, y + 6);\n c.setTransform(1, 0, 0, 1, x + 8.159, y);\n c.beginPath();\n c.moveTo(0, 12);\n c.bezierCurveTo(0, 5.37258, 1.71967, 0, 3.841, 0);\n c.bezierCurveTo(5.96233, 0, 7.682, 5.37258, 7.682, 12);\n c.bezierCurveTo(7.682, 18.62742, 5.96233, 24, 3.841, 24);\n c.bezierCurveTo(1.71967, 24, 0, 18.62742, 0, 12);\n c.fillStyle = '#EDD500';\n c.fill('nonzero');\n c.setTransform(1, 0, 0, 1, x + 5.9, y + 2.427);\n c.beginPath();\n c.moveTo(18.1, 10.141);\n c.bezierCurveTo(14.52911, 12.50002, 10.37702, 13.82904, 6.1, 13.982);\n c.bezierCurveTo(1.82298, 13.82904, -2.3291, 12.50002, -5.9, 10.141);\n c.bezierCurveTo(-2.32911, 7.78198, 1.82299, 6.45296, 6.1, 6.3);\n c.bezierCurveTo(10.37702, 6.45296, 14.5291, 7.78198, 18.1, 10.141);\n // c.closePath();\n c.fill('nonzero');\n};\n\nconst drawTriangle = (c, { x, y }, percent) => {\n if (percent) {\n c.font = '16px Roboto';\n c.fillStyle = '#EDD500';\n c.fillText(`${percent}%`, x - 10, y - 20);\n }\n\n const side = 26;\n const h = side * (Math.sqrt(3) / 2);\n\n c.fillStyle = '#EDD500';\n\n // c.save();\n c.translate(x, y);\n\n // c.beginPath();\n\n c.moveTo(0, -h / 2);\n c.lineTo(-side / 2, h / 2);\n c.lineTo(side / 2, h / 2);\n c.lineTo(0, -h / 2);\n c.stroke();\n c.fill();\n};\n\nconst shapeMaps = {\n arrow: drawArrow,\n 3: drawFlake,\n 1: drawCross,\n 2: drawTriangle,\n};\n\nconst drawHandles = (context, evtDetail, handles, options = {}) => {\n const element = evtDetail.element;\n const defaultColor = toolColors.getToolColor();\n\n context.strokeStyle = options.color || defaultColor;\n\n const handleKeys = Object.keys(handles);\n\n for (let i = 0; i < handleKeys.length; i++) {\n const handleKey = handleKeys[i];\n const handle = handles[handleKey];\n\n if (handle.drawnIndependently === true) {\n continue;\n }\n\n if (options.drawHandlesIfActive === true && !handle.active) {\n continue;\n }\n\n const lineWidth = handle.active ? toolStyle.getActiveWidth() : toolStyle.getToolWidth();\n const fillStyle = options.fill;\n const shape = handle.shape || 'arrow';\n\n path(\n context,\n {\n lineWidth,\n fillStyle,\n },\n context => {\n const handleCanvasCoords = cornerstone.pixelToCanvas(element, handle);\n shapeMaps[shape](context, handleCanvasCoords, handle.percentage);\n },\n );\n }\n};\n\nexport default drawHandles;\n","/* eslint-disable import/no-anonymous-default-export */\n/**\n * Create a new {@link CanvasRenderingContext2D|context} object for the given {@link HTMLCanvasElement|canvas}\n * and set the transform to the {@link https://www.w3.org/TR/2dcontext/#transformations|identity transform}.\n *\n * @public\n * @function getNewContext\n * @memberof Drawing\n *\n * @param {HTMLCanvasElement} canvas - Canvas you would like the context for\n * @returns {CanvasRenderingContext2D} - The provided canvas's 2d context\n */\nexport default function(canvas) {\n const context = canvas.getContext('2d');\n\n context.setTransform(1, 0, 0, 1, 0, 0);\n\n return context;\n}\n","import toolColors from './../stateManagement/toolColors.js';\n\nconst configuration = {\n iconSize: 16,\n viewBox: {\n x: 16,\n y: 16,\n },\n mousePoint: {\n x: 8,\n y: 8,\n },\n mousePointerGroupString: `\n \n \n `,\n};\n\n/* eslint-disable valid-jsdoc */\n\n/*\nMACROS - The following keys will have the appropriate value injected when\nan SVG is requested:\n\n- ACTIVE_COLOR => options.activeColor || toolColors.getActiveColor();\n- TOOL_COLOR => options.toolColor || toolColors.getToolColor();\n- FILL_COLOR => options.fillColor || toolColors.getFillColor();\n*/\n\nclass MouseCursor {\n constructor(iconGroupString, options) {\n this.iconGroupString = iconGroupString;\n this.options = Object.assign({}, configuration, options);\n }\n\n /**\n * Returns an SVG of the icon only.\n *\n * @param {Object} options - An object which overrides default properties of the returned SVG.\n * @returns {Blob} The SVG of the icon.\n */\n getIconSVG(options = {}) {\n const svgString = this._generateIconSVGString(options);\n\n return new Blob([svgString], { type: 'image/svg+xml' });\n }\n\n /**\n * Returns a string representation of the SVG of the icon only.\n *\n * @param {Object} options - An object which overrides default properties of the returned SVG.\n * @returns {string} The stringified SVG of the icon.\n */\n getIconSVGString(options = {}) {\n return this._generateIconSVGString(options);\n }\n\n /**\n * Returns an SVG of the icon + pointer.\n *\n * @param {Object} options - An object which overrides default properties of the returned SVG.\n * @returns {Blob} The SVG of the icon + pointer..\n */\n getIconWithPointerSVG(options = {}) {\n const svgString = this._generateIconWithPointerSVGString(options);\n\n return new Blob([svgString], { type: 'image/svg+xml' });\n }\n\n /**\n * Returns a string representation of the SVG of the icon + pointer.\n *\n * @param {Object} options - An object which overrides default properties of the returned SVG.\n * @returns {string} The stringified SVG of the icon + pointer.\n */\n getIconWithPointerString(options = {}) {\n return this._generateIconWithPointerSVGString(options);\n }\n\n /**\n * Returns the mousePoint as a space seperated string.\n *\n * @returns {string} The mousePoint.\n */\n get mousePoint() {\n const mousePoint = this.options.mousePoint;\n\n return `${mousePoint.x} ${mousePoint.y}`;\n }\n\n /**\n * Generates a string representation of the icon + pointer.\n *\n * @param {Object} options - An object which overrides default properties of the returned string.\n * @returns {string} The SVG as a string.\n */\n _generateIconWithPointerSVGString(options = {}) {\n const svgOptions = Object.assign({}, this.options, options);\n const { mousePointerGroupString, iconSize, viewBox } = svgOptions;\n\n const scale = iconSize / Math.max(viewBox.x, viewBox.y);\n const svgSize = 16 + iconSize;\n\n const svgString = `\n \n \n ${mousePointerGroupString}\n \n \n ${this.iconGroupString}\n \n `;\n\n return this._injectColors(svgString, svgOptions);\n }\n\n /**\n * Generates a string representation of the icon.\n *\n * @param {Object} options - An object which overrides default properties of the returned string.\n * @returns {string} The SVG as a string.\n */\n _generateIconSVGString(options = {}) {\n const svgOptions = Object.assign({}, this.options, options);\n const { iconSize, viewBox } = svgOptions;\n\n const svgString = `\n \n ${this.iconGroupString}\n `;\n\n return this._injectColors(svgString, svgOptions);\n }\n\n /**\n * Replaces ACTIVE_COLOR, TOOL_COLOR and FILL_COLOR in svgString with their appropriate values.\n *\n * @param {string} svgString - The string to modify.\n * @param {Object} options - Optional overrides for the colors.\n * @returns {string} The string with color values injected.\n */\n _injectColors(svgString, options = {}) {\n const activeColor = options.activeColor || toolColors.getActiveColor();\n const toolColor = options.toolColor || toolColors.getToolColor();\n const fillColor = options.fillColor || toolColors.getFillColor();\n\n return svgString\n .replace(/ACTIVE_COLOR/g, `${activeColor}`)\n .replace(/TOOL_COLOR/g, `${toolColor}`)\n .replace(/FILL_COLOR/g, `${fillColor}`);\n }\n}\n\nexport const probeCursor = new MouseCursor(\n ` `,\n {\n viewBox: {\n x: 1792,\n y: 1792,\n },\n },\n);\n","import csTools from 'cornerstone-tools';\nimport * as cornerstone from 'cornerstone-core';\nimport cornerstoneMath from 'cornerstone-math';\n// State\nimport { getToolState, addToolState, removeToolState } from './stateManagement/toolState.js';\nimport toolColors from './stateManagement/toolColors.js';\n// Drawing\nimport { getNewContext, draw } from './drawing/index.js';\nimport drawHandles from './drawing/drawHandles.js';\n// Utilities\nimport { probeCursor } from './cursors/index.js';\n\nconst BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool');\n\nexport default class ArrowTool extends BaseAnnotationTool {\n constructor(props = {}) {\n const defaultProps = {\n name: 'ArrowTool',\n supportedInteractionTypes: ['Mouse', 'Touch'],\n svgCursor: probeCursor,\n configuration: {\n drawHandles: true,\n },\n };\n\n super(props, defaultProps);\n this.touchPressCallback = this._onClickArrow.bind(this);\n this.doubleClickCallback = this._onClickArrow.bind(this);\n }\n\n createEventData({ x, y }) {\n return {\n visible: true,\n active: true,\n color: undefined,\n invalidated: true,\n handles: {\n end: {\n x,\n y,\n highlight: true,\n active: true,\n },\n },\n };\n }\n\n createNewMeasurement(eventData) {\n const { onArrowAdd } = this.initialConfiguration;\n const goodEventData = eventData && eventData.currentPoints && eventData.currentPoints.image;\n if (!goodEventData) {\n return {};\n }\n\n if (onArrowAdd) onArrowAdd(eventData.image, eventData);\n return this.createEventData(eventData.currentPoints.image);\n }\n\n pointNearTool(element, data, coords) {\n const hasEndHandle = data && data.handles && data.handles.end;\n const validParameters = hasEndHandle;\n if (!validParameters || data.visible === false) {\n return false;\n }\n const probeCoords = cornerstone.pixelToCanvas(element, data.handles.end);\n return cornerstoneMath.point.distance(probeCoords, coords) < 30;\n }\n\n updateCachedStats(image, element, data) {\n const { onArrowMove } = this.initialConfiguration;\n clearTimeout(this.updateEvent);\n\n this.updateEvent = setTimeout(() => {\n if (onArrowMove) onArrowMove(image, data);\n }, 500);\n }\n\n createInitialData(handles) {\n const data = [];\n handles.forEach(item => {\n data.push(this.createEventData(item));\n });\n return this.createEventData(handles[0]);\n }\n\n addedToolInitialData(target) {\n const { data } = this.initialConfiguration;\n if (this.addedInitialData || !data || !data.length) return;\n data.forEach(item => {\n addToolState(target, this.name, this.createEventData(item));\n });\n this.addedInitialData = true;\n }\n\n renderToolData(evt) {\n this.addedToolInitialData(evt.currentTarget);\n const eventData = evt.detail;\n const { handleRadius } = this.configuration;\n const toolData = getToolState(evt.currentTarget, this.name);\n\n if (!toolData) {\n return;\n }\n\n const context = getNewContext(eventData.canvasContext.canvas);\n\n for (let i = 0; i < toolData.data.length; i++) {\n const data = toolData.data[i];\n\n if (data.visible === false) {\n continue;\n }\n\n draw(context, context => {\n const color = toolColors.getColorIfActive(data);\n if (this.configuration.drawHandles) {\n drawHandles(context, eventData, data.handles, {\n handleRadius,\n color,\n });\n }\n });\n }\n }\n\n _onClickArrow(evt) {\n if (!window.confirm('Remove Arrow ?')) return;\n const { onArrowRemove } = this.initialConfiguration;\n const eventData = evt.detail;\n const { element, currentPoints } = eventData;\n\n const coords = currentPoints.canvas;\n const toolData = getToolState(element, this.name);\n\n // Now check to see if there is a handle we can move\n if (!toolData) {\n return;\n }\n\n for (let i = 0; i < toolData.data.length; i++) {\n const data = toolData.data[i];\n if (this.pointNearTool(element, data, coords)) {\n removeToolState(element, this.name, data);\n if (onArrowRemove) onArrowRemove(eventData.image, data);\n cornerstone.updateImage(element);\n\n evt.stopImmediatePropagation();\n evt.preventDefault();\n evt.stopPropagation();\n\n return;\n }\n }\n }\n}\n","import csTools from 'cornerstone-tools';\nimport * as cornerstone from 'cornerstone-core';\nimport cornerstoneMath from 'cornerstone-math';\n// State\nimport { getToolState, addToolState, removeToolState } from './stateManagement/toolState.js';\n// Drawing\nimport { getNewContext, draw } from './drawing/index.js';\nimport drawHandles from './drawing/drawHandles.js';\n// Utilities\nimport { probeCursor } from './cursors/index.js';\n\nconst BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool');\n\nexport default class GenuineTool extends BaseAnnotationTool {\n constructor(props = {}) {\n const defaultProps = {\n name: 'GenuineTool',\n supportedInteractionTypes: ['Mouse', 'Touch'],\n svgCursor: probeCursor,\n configuration: {\n drawHandles: true,\n },\n };\n\n super(props, defaultProps);\n this.touchPressCallback = this._onDBClickPoint.bind(this);\n this.doubleClickCallback = this._onDBClickPoint.bind(this);\n }\n\n activeCallback(element, data) {\n this.shape = data.shape;\n }\n\n disabledCallback(element) {}\n\n createEventData({ x, y, shape, percentage }) {\n return {\n visible: true,\n active: true,\n color: undefined,\n invalidated: true,\n handles: {\n end: {\n percentage: percentage,\n shape: shape || this.shape,\n x,\n y,\n highlight: true,\n active: true,\n },\n },\n };\n }\n\n createNewMeasurement(eventData) {\n // const { onArrowAdd } = this.initialConfiguration;\n const goodEventData = eventData && eventData.currentPoints && eventData.currentPoints.image;\n if (!goodEventData) {\n return {};\n }\n\n const currentDataLength = getToolState(eventData.element, this.name)?.data.length;\n\n setTimeout(() => {\n const toolData = getToolState(eventData.element, this.name);\n if (!toolData || toolData.data?.length === currentDataLength) return;\n const percentage = prompt('Probability (Enter %):');\n this._onUpdatePoint(eventData, percentage, !percentage);\n }, 400);\n\n // if (onArrowAdd) onArrowAdd(eventData.image, eventData);\n return this.createEventData({ ...eventData.currentPoints.image, percentage: null });\n }\n\n pointNearTool(element, data, coords) {\n const hasEndHandle = data && data.handles && data.handles.end;\n const validParameters = hasEndHandle;\n if (!validParameters || data.visible === false) {\n return false;\n }\n const probeCoords = cornerstone.pixelToCanvas(element, data.handles.end);\n return cornerstoneMath.point.distance(probeCoords, coords) < 30;\n }\n\n updateCachedStats(image, element, data) {\n const { onArrowMove } = this.initialConfiguration;\n clearTimeout(this.updateEvent);\n\n this.updateEvent = setTimeout(() => {\n if (onArrowMove) onArrowMove(image, data);\n }, 500);\n }\n\n createInitialData(handles) {\n const data = [];\n handles.forEach(item => {\n data.push(this.createEventData(item));\n });\n return this.createEventData(handles[0]);\n }\n\n addedToolInitialData(target) {\n const { data } = this.initialConfiguration;\n if (this.addedInitialData || !data || !data.length) return;\n data.forEach(item => {\n addToolState(target, this.name, this.createEventData(item));\n });\n this.addedInitialData = true;\n }\n\n renderToolData(evt) {\n this.addedToolInitialData(evt.currentTarget);\n const eventData = evt.detail;\n const { handleRadius } = this.configuration;\n const toolData = getToolState(evt.currentTarget, this.name);\n\n if (!toolData) {\n return;\n }\n\n const context = getNewContext(eventData.canvasContext.canvas);\n\n for (let i = 0; i < toolData.data.length; i++) {\n const data = toolData.data[i];\n\n if (data.visible === false) {\n continue;\n }\n\n draw(context, context => {\n // const color = toolColors.getColorIfActive(data);\n if (this.configuration.drawHandles) {\n drawHandles(context, eventData, data.handles, {\n handleRadius,\n color: '#EDD500',\n });\n }\n });\n }\n }\n\n _onDBClickPoint(evt) {\n const percentage = prompt('Update Probability (Enter %):');\n const eventData = evt.detail;\n\n this._onUpdatePoint(eventData, percentage, !percentage);\n\n evt.stopImmediatePropagation();\n evt.preventDefault();\n evt.stopPropagation();\n\n return;\n }\n\n _onUpdatePoint(eventData, percentage, isRemove) {\n const { onArrowMove, onArrowRemove } = this.initialConfiguration;\n const { element, currentPoints } = eventData;\n\n const coords = currentPoints.canvas;\n const toolData = getToolState(element, this.name);\n\n // Now check to see if there is a handle we can move\n if (!toolData) {\n return;\n }\n\n for (let i = 0; i < toolData.data.length; i++) {\n const data = toolData.data[i];\n if (this.pointNearTool(element, data, coords)) {\n if (isRemove) {\n removeToolState(element, this.name, data);\n if (onArrowRemove) onArrowRemove(eventData.image, data);\n } else {\n data.handles.end.percentage = percentage;\n if (onArrowMove) onArrowMove(eventData.image, data);\n }\n\n cornerstone.updateImage(element);\n\n return;\n }\n }\n }\n}\n","/* eslint-disable no-console */\nimport React, { useRef, useEffect, useState } from 'react';\nimport {\n zoomImageMouseClick,\n maximazeElement,\n getFindingsCoordinates,\n createFindingEventData,\n getDicomImagesObj,\n createGenuieEventData,\n sumShapes,\n} from '../utils';\nimport Thumbnail from './Thumbnail';\n// import ScaleRules from './ScaleRules';\nimport StackControl from './StackControl';\n\nimport * as cornerstone from 'cornerstone-core';\nimport cornerstoneMath from 'cornerstone-math';\nimport cornerstoneTools from 'cornerstone-tools/dist/cornerstoneTools';\nimport * as cornerstoneWebImageLoader from 'cornerstone-web-image-loader';\nimport Hammer from 'hammerjs';\nimport Loading from 'shared/components/Loading';\nimport ArrowTool from '../plugins/ArrowTool';\nimport GenuineTool from '../plugins/GenuineTool';\n\nimport iconMax from 'assets/arrows/2p.svg';\nimport { genuieShapes, getDefaultType, getToolByType } from '../configs';\nimport { useDispatch } from 'react-redux';\nimport { setCanBeActive } from '../actions';\nimport { getToolState } from '../plugins/stateManagement/toolState';\nimport { IconGenuine } from 'shared/components/Icons';\n\ncornerstoneWebImageLoader.external.cornerstone = cornerstone;\ncornerstoneTools.external.cornerstone = cornerstone;\ncornerstoneTools.external.Hammer = Hammer;\ncornerstoneTools.external.cornerstoneMath = cornerstoneMath;\n\ncornerstoneTools.init({ mouseEnabled: true, showSVGCursors: false, autoResizeViewports: true });\ncornerstoneTools.toolStyle.setToolWidth('4');\n\nconst switchers = [\n { name: '2D', value: '2D' },\n { name: 'Tomo', value: 'tomo' },\n { name: '3DQ', value: '3DQ' },\n];\n\nconst CanvasView = ({\n dataTomo,\n data2D,\n data3DQ,\n activeTool,\n step,\n fullScreen,\n viewsCount,\n setActiveTool,\n updateFinding,\n viewSizesForced,\n currentImageIndex = 0,\n}) => {\n const dicomTypeData = {\n tomo: dataTomo,\n '2D': data2D,\n '3DQ': data3DQ,\n };\n\n const dispatch = useDispatch();\n const [play, setPlay] = useState(false);\n const [showGenuie, setShowGenuie] = useState(true);\n const [imageFetch, setImageFetch] = useState(false);\n const [stackControl, setStackControl] = useState(getDefaultType(step.dataType, dicomTypeData));\n const [viewportState, setViewportState] = useState();\n const element = useRef(null);\n const isFirstRun = useRef(true);\n\n const dicomData = dicomTypeData[stackControl];\n\n const getViewSizes = () => {\n if (viewSizesForced) return viewSizesForced();\n return {\n width: (window.innerWidth - (fullScreen ? 0 : 240)) / viewsCount,\n height: window.innerHeight - 95,\n };\n };\n\n const onArrowUpdate = image => {\n setTimeout(() => {\n try {\n const viewport = cornerstone.getViewport(element.current);\n const currentImage = viewport.images[image.imageId];\n if (!currentImage) return;\n const { emptyFinding, finding } = currentImage;\n const cuurentArrowData = getToolState(element.current, 'ArrowTool');\n const body = { ...(finding || emptyFinding) };\n body.vectorData = getFindingsCoordinates(cuurentArrowData);\n updateFinding(body);\n } catch (err) {\n console.log(err);\n }\n }, 100);\n };\n\n const updateCornerstone = () => {\n try {\n const viewport = cornerstone.getViewport(element.current);\n if (!viewport) return;\n cornerstone.setViewport(element.current, viewport);\n } catch (err) {\n console.log(err);\n }\n };\n\n const initFindings = () => {\n try {\n cornerstoneTools.addToolForElement(element.current, ArrowTool, {\n onArrowMove: onArrowUpdate,\n onArrowAdd: onArrowUpdate,\n onArrowRemove: onArrowUpdate,\n });\n dispatch(setCanBeActive('findingToggle', false));\n cornerstoneTools.setToolEnabledForElement(element.current, 'ArrowTool');\n setActiveToolEvent();\n setTimeout(updateCornerstone, 1000);\n } catch (err) {\n console.log(err);\n }\n };\n\n const initGenuines = () => {\n try {\n cornerstoneTools.addToolForElement(element.current, GenuineTool, {});\n cornerstoneTools.setToolEnabledForElement(element.current, 'GenuineTool');\n setActiveToolEvent();\n setTimeout(updateCornerstone, 1000);\n } catch (err) {\n console.log(err);\n }\n };\n\n const updateCurrentImageFindings = ({ image }, viewport) => {\n const imageInfo = viewport.images[image.imageId];\n if (!imageInfo) return;\n const findings = imageInfo.finding;\n if (findings && findings.vectorData && !imageInfo.inited) {\n findings.vectorData.forEach(item => {\n cornerstoneTools.addToolState(element.current, 'ArrowTool', createFindingEventData(item));\n });\n }\n imageInfo.inited = true;\n };\n\n const updateCurrentImageGenuine = ({ image }, viewport) => {\n const imageInfo = viewport.images[image.imageId];\n if (!imageInfo) return;\n const points = imageInfo.geniusAIDataList;\n if (points && points.length && !imageInfo.genuineInited) {\n points.forEach(item => {\n cornerstoneTools.addToolState(element.current, 'GenuineTool', createGenuieEventData(item));\n });\n }\n imageInfo.genuineInited = true;\n };\n\n const onImageRendered = (e, v) => {\n try {\n if (!element.current) return;\n const viewport = cornerstone.getViewport(element.current);\n updateCurrentImageFindings(e.detail, viewport);\n updateCurrentImageGenuine(e.detail, viewport);\n setViewportState(viewport);\n } catch (err) {\n console.log(err);\n }\n };\n\n const onWindowResize = () => {\n cornerstone.resize(element.current);\n setElementDisplayPosition();\n };\n\n const setElementDisplayPosition = notMove => {\n const viewport = cornerstone.getViewport(element.current);\n if (!viewport) return;\n // const isLeft = step.position === 'left' && step.thumbpos === 'left';\n const { image, scale } = viewport;\n const { clientWidth } = element.current;\n const imageWidth = image.width; //!stackControl ? image.width : image.width + (isLeft ? -1000 : 0);\n const translation = { x: clientWidth / scale / 2 - imageWidth / 2, y: 0 };\n if (step.position === 'left') translation.x = -translation.x;\n if (!notMove) viewport.translation = translation;\n viewport.initialScale = scale;\n viewport.voi = { windowWidth: 255, windowCenter: 128 };\n cornerstone.setViewport(element.current, viewport);\n };\n\n const setImageStack = element => {\n const imageIds = dicomData.images.map(item => item.url);\n const stack = { currentImageIdIndex: 0, imageIds };\n cornerstoneTools.addStackStateManager(element, ['stack']);\n cornerstoneTools.addToolState(element, 'stack', stack);\n };\n\n const loadImage = async () => {\n if (!element.current) return;\n setImageFetch(true);\n try {\n const images = getDicomImagesObj(dicomData.images);\n const dicomImage = dicomData.images[currentImageIndex] || dicomData.images[0];\n const image = await cornerstone.loadAndCacheImage(dicomImage.url);\n const viewportOptions = { pixelReplication: false, image, dicomImage, images };\n await cornerstone.displayImage(element.current, image, viewportOptions);\n } catch (err) {\n console.log(err);\n } finally {\n setImageFetch(false);\n }\n };\n\n const onRightClick = (el, e) => {\n if (e.which !== 3) return;\n e.preventDefault();\n let tool;\n setActiveTool(item => {\n tool = item;\n return getToolByType('pan');\n });\n\n const handleMouseUp = (tool, e) => {\n if (e.which !== 3) return;\n el.removeEventListener('mouseup', handleMouseUp.bind(null, tool), false);\n const setTool = tool && tool.type && tool.type !== 'reset' ? tool : { type: 'none' };\n setActiveTool(setTool);\n };\n\n el.addEventListener('mouseup', handleMouseUp.bind(null, tool), false);\n // eslint-disable-next-line consistent-return\n return false;\n };\n\n const initElement = async element => {\n try {\n await cornerstone.enable(element);\n await cornerstone.resize(element);\n element.addEventListener('cornerstoneimagerendered', onImageRendered);\n window.addEventListener('resize', onWindowResize);\n element.addEventListener('mousedown', onRightClick.bind(null, element), false);\n element.addEventListener('contextmenu', e => e.preventDefault(), false);\n } catch (err) {\n console.log(err);\n }\n };\n\n const initData = async element => {\n try {\n if (!dicomData) return;\n await element.classList.remove('maximize');\n await setImageStack(element);\n await loadImage();\n await cornerstone.fitToWindow(element);\n await cornerstone.resize(element);\n setElementDisplayPosition();\n setActiveToolEvent();\n setTimeout(initFindings, 100);\n setTimeout(initGenuines, 100);\n } catch (err) {\n console.log(err);\n }\n };\n\n const unmountElement = async () => {\n try {\n element.current.removeEventListener('cornerstoneimagerendered', onImageRendered);\n window.removeEventListener('resize', onWindowResize);\n cornerstone.disable(element.current);\n } catch (err) {\n console.log(err);\n }\n };\n\n const removeTool = name => {\n const { current } = element;\n if (cornerstoneTools.getToolForElement(current, name)) {\n cornerstoneTools.removeToolForElement(current, name);\n }\n };\n\n const setActiveToolEvent = async () => {\n try {\n const viewport = cornerstone.getViewport(element.current);\n if (!viewport || !activeTool.type) return;\n const { type, onTimeAction, noResetFindings } = activeTool;\n const { current } = element;\n current.onclick = null;\n\n removeTool('Pan');\n removeTool('Wwwc');\n removeTool('Zoom');\n removeTool('ZoomMouseWheel');\n\n if (type === 'none' || (onTimeAction && !noResetFindings)) {\n cornerstoneTools.setToolEnabledForElement(current, 'ArrowTool');\n }\n\n if (type === 'reset') {\n current.classList.remove('maximize');\n setTimeout(async () => {\n await cornerstone.resize(current);\n await cornerstone.fitToWindow(current);\n await setElementDisplayPosition();\n });\n } else if (type === 'pan') {\n cornerstoneTools.addToolForElement(current, cornerstoneTools.PanTool);\n cornerstoneTools.setToolActive('Pan', { mouseButtonMask: 1 });\n cornerstoneTools.setToolActive('Pan', { mouseButtonMask: 2 });\n } else if (type === 'zoomInteractive') {\n const options = {\n configuration: {\n invert: false,\n preventZoomOutsideImage: false,\n minScale: 0.01,\n maxScale: 5.0,\n },\n };\n cornerstoneTools.addToolForElement(current, cornerstoneTools.ZoomMouseWheelTool, options);\n cornerstoneTools.addToolForElement(current, cornerstoneTools.ZoomTool, options);\n cornerstoneTools.setToolActive('ZoomMouseWheel', { mouseButtonMask: 1 });\n cornerstoneTools.setToolActive('Zoom', { mouseButtonMask: 1 });\n } else if (type === 'zoomMagnifier') {\n current.onclick = zoomImageMouseClick.bind(null, cornerstone, current, true, false);\n } else if (type === 'windowLevel') {\n cornerstoneTools.addToolForElement(current, cornerstoneTools.WwwcTool);\n cornerstoneTools.setToolActive('Wwwc', { mouseButtonMask: 1 });\n } else if (type === 'finding') {\n cornerstoneTools.setToolActive('ArrowTool', { mouseButtonMask: 1 });\n } else if (type === 'findingToggle') {\n const tool = cornerstoneTools.getToolForElement(current, 'ArrowTool');\n const isDisabled = tool && tool.mode === 'disabled';\n dispatch(setCanBeActive(type, !isDisabled));\n if (isDisabled) {\n cornerstoneTools.setToolEnabledForElement(current, 'ArrowTool');\n } else {\n cornerstoneTools.setToolDisabledForElement(current, 'ArrowTool');\n }\n setTimeout(updateCornerstone, 500);\n } else if (type === 'findingReset') {\n cornerstoneTools.clearToolState(current, 'ArrowTool');\n setTimeout(updateCornerstone, 100);\n } else if (type === 'zoom1_1') {\n current.onclick = () => {\n maximazeElement(cornerstone, current);\n zoomImageMouseClick(cornerstone, current, true, 1);\n };\n }\n } catch (err) {\n console.log(err);\n }\n };\n\n const toggleMax = async () => {\n maximazeElement(cornerstone, element.current, true);\n setElementDisplayPosition(true);\n };\n\n const toggleGenuie = () => {\n if (showGenuie) {\n cornerstoneTools.setToolDisabledForElement(element.current, 'GenuineTool');\n } else {\n cornerstoneTools.setToolEnabledForElement(element.current, 'GenuineTool');\n }\n setTimeout(updateCornerstone, 200);\n setShowGenuie(!showGenuie);\n };\n\n const fullScreenChange = async () => {\n if (!element || !element.current) return;\n try {\n setTimeout(() => {\n cornerstone.resize(element.current);\n cornerstone.fitToWindow(element.current);\n setElementDisplayPosition();\n }, 500);\n } catch (err) {\n console.log(err);\n }\n };\n\n useEffect(() => {\n initElement(element.current);\n return () => unmountElement();\n //eslint-disable-next-line\n }, [stackControl]);\n\n useEffect(() => {\n initData(element.current);\n //eslint-disable-next-line\n }, [dataTomo, step, stackControl]);\n\n useEffect(() => {\n if (element.current) onWindowResize();\n //eslint-disable-next-line\n }, [step]);\n\n useEffect(() => {\n setActiveToolEvent();\n //eslint-disable-next-line\n }, [activeTool]);\n\n useEffect(() => {\n if (isFirstRun.current) {\n isFirstRun.current = false;\n return;\n }\n fullScreenChange();\n //eslint-disable-next-line\n }, [fullScreen]);\n\n const showSlice = element && viewportState && stackControl !== '2D' && !currentImageIndex;\n const hasGenuie = !!dicomData?.images.filter(item => !!item.geniusAIDataList?.length).length;\n\n return (\n \n {hasGenuie && (\n
\n
\n \n \n {showGenuie && (\n
\n {genuieShapes.map((item, i) => {\n const Icon = item.icon;\n const shapesCount = sumShapes(dicomData.images, item.shape);\n return (\n \n \n {!!shapesCount && {shapesCount} }\n \n );\n })}\n
\n )}\n
\n )}\n {viewportState &&
}\n {dicomData && (\n
\n
\n
\n \n \n
\n 0{dicomData.patient.age}Y {dicomData.imageLaterality} {dicomData.viewPosition}\n \n
\n
\n )}\n {step.has_switcher && (\n
\n
\n {switchers.map(item => (\n setStackControl(item.value)}\n className={stackControl === item.value ? 'active' : ''}\n >\n {item.name}\n \n ))}\n
\n
\n )}\n {/* {viewportState && (\n
\n )} */}\n {showSlice && dicomData && (\n
\n )}\n {imageFetch &&
}\n {dicomData &&
}\n {!dicomData && (\n
\n No Image Available\n
\n )}\n
\n );\n};\n\nexport default CanvasView;\n","import React, { useRef } from 'react';\nimport { openFullscreen, closeFullscreen } from 'utils/appHelpers';\n\nimport iconMax from 'assets/arrows/2p.svg';\nimport iconMin from 'assets/arrows/4a.svg';\nimport BackButton from 'shared/BackButton';\nimport useOutsideClick from 'shared/hooks/useOutsideClick';\nimport { saveToStore, getFromStore } from 'utils/storeHelpers';\n\nconst ControlArea = ({\n tools,\n course,\n fullScreen,\n activeTool,\n setActiveTool,\n history,\n viewerOptions,\n disabled,\n}) => {\n const toolClickHandle = item => {\n const state =\n item.type === activeTool.type && !item.onTimeAction ? { type: 'none' } : { ...item };\n setActiveTool(state);\n };\n\n const tooltipRef = useRef();\n\n useOutsideClick(tooltipRef, () => tooltipRef.current.classList.remove('hover'));\n\n return (\n \n
\n
\n {!fullScreen && history &&
}\n
{\n tooltipRef.current.classList.remove('hover');\n saveToStore('viewerToolTip', true);\n }}\n >\n \n e.stopPropagation()}\n >\n {fullScreen ? 'Normal ' : 'Full '}Screen \n \n Click this button to\n {fullScreen ? ' turn off the full screen' : ' display the viewer in full screen'}\n \n \n {course &&
{course.title}
}\n
\n
\n
\n
\n {tools.map(item => {\n const isSeparator = item.type === 'separator';\n const canActive = viewerOptions && viewerOptions.canBeActive[item.type];\n const isActive = (activeTool.type === item.type && !item.onTimeAction) || canActive;\n if (isSeparator) return
;\n return (\n
\n {item.icon && (\n \n )}\n \n );\n })}\n
\n
\n
\n
\n
\n );\n};\n\nexport default ControlArea;\n","import step2Icon from 'assets/stepsnew/2n.svg';\nimport step2IconActive from 'assets/stepsnew/2s.svg';\nimport step2IconPassed from 'assets/stepsnew/2p.svg';\nimport step2IconPActive from 'assets/stepsnew/2ps.svg';\n\nimport step3Icon from 'assets/stepsnew/3n.svg';\nimport step3IconActive from 'assets/stepsnew/3s.svg';\nimport step3IconPassed from 'assets/stepsnew/3p.svg';\nimport step3IconPActive from 'assets/stepsnew/3ps.svg';\n\nimport step4Icon from 'assets/stepsnew/4n.svg';\nimport step4IconActive from 'assets/stepsnew/4s.svg';\nimport step4IconPassed from 'assets/stepsnew/4p.svg';\nimport step4IconPActive from 'assets/stepsnew/4ps.svg';\n\nimport tool4Icon from 'assets/tools/4p.svg';\nimport tool4IconActive from 'assets/tools/4a.svg';\nimport tool5Icon from 'assets/tools/5p.svg';\nimport tool5IconActive from 'assets/tools/5a.svg';\nimport tool6Icon from 'assets/tools/6p.svg';\nimport tool6IconActive from 'assets/tools/6a.svg';\nimport tool7Icon from 'assets/tools/7p.svg';\nimport tool7IconActive from 'assets/tools/7a.svg';\nimport tool8Icon from 'assets/tools/8p.svg';\nimport tool8IconActive from 'assets/tools/8a.svg';\nimport tool9Icon from 'assets/tools/9p.svg';\nimport tool9IconActive from 'assets/tools/9a.svg';\n\nconst dicomTypes = {\n '3dq': '3DQ',\n i2d: '2D',\n g2d: '2D',\n r2d: '2D',\n tomo: 'tomo',\n sub: 'tomo',\n};\n\nexport const stepsProperties = {\n RCC: {\n active: true,\n position: 'right',\n thumbpos: 'left',\n has_switcher: false,\n dataType: '2D',\n key: 'RCC',\n },\n LCC: {\n active: true,\n position: 'left',\n thumbpos: 'right',\n has_switcher: false,\n dataType: '2D',\n key: 'LCC',\n },\n RMLO: {\n active: true,\n key: 'RMLO',\n position: 'right',\n thumbpos: 'left',\n has_switcher: false,\n dataType: '2D',\n },\n LMLO: {\n active: true,\n key: 'LMLO',\n position: 'left',\n thumbpos: 'right',\n has_switcher: false,\n dataType: '2D',\n },\n};\n\nexport const answerSteps = [\n {\n id: 1,\n name: '2D CC Views',\n icon: step2Icon,\n iconActive: step2IconActive,\n iconPassed: step2IconPassed,\n iconPassedActive: step2IconPActive,\n },\n {\n id: 2,\n name: '2D MLO Views',\n icon: step3Icon,\n iconActive: step3IconActive,\n iconPassed: step3IconPassed,\n iconPassedActive: step3IconPActive,\n },\n {\n id: 3,\n name: '2D/3D RCC Views',\n icon: step4Icon,\n iconActive: step4IconActive,\n iconPassed: step4IconPassed,\n iconPassedActive: step4IconPActive,\n },\n];\n\nexport const answerTools = [\n {\n name: 'R',\n title: 'Reset Viewers',\n icon: tool4Icon,\n iconActive: tool4IconActive,\n type: 'reset',\n cursor: 'default',\n onTimeAction: true,\n },\n {\n name: 'P',\n title: 'Activate Panning Tool',\n icon: tool5Icon,\n iconActive: tool5IconActive,\n type: 'pan',\n cursor: 'pan',\n onTimeAction: false,\n },\n {\n name: 'I Z',\n title: 'Activate Interactive Zoom',\n icon: tool6Icon,\n iconActive: tool6IconActive,\n type: 'zoomInteractive',\n cursor: 'izoom',\n onTimeAction: false,\n },\n {\n name: 'Z 1:1',\n title: 'Activate 1:1 Tool',\n icon: tool7Icon,\n iconActive: tool7IconActive,\n type: 'zoom1_1',\n cursor: 'fullzoom',\n onTimeAction: false,\n },\n {\n name: 'M',\n title: 'Activate Magnifier Tool',\n icon: tool8Icon,\n iconActive: tool8IconActive,\n type: 'zoomMagnifier',\n cursor: 'mzoom',\n onTimeAction: false,\n },\n {\n name: 'W/L',\n title:\n 'Activate Window/Level Tool (W; for presets move mouse over viewer and press numeric keys 1, 2, 3, ...)',\n icon: tool9Icon,\n iconActive: tool9IconActive,\n type: 'windowLevel',\n cursor: 'wlevel',\n onTimeAction: false,\n },\n];\n\nexport const getToolByType = type => {\n return answerTools.find(item => item.type === type);\n};\n\nexport const mergeSteps = (views, backSteps) => {\n if (!backSteps || !backSteps.length) return [];\n const result = backSteps.map((item, index) => {\n const step = backSteps[index];\n item.stepType = step.stepType;\n if (step.stepType === 'case') {\n const left = step.caseLeftFrame;\n const right = step.caseRightFrame;\n const leftDicom = Object.values(views).find(d => d.images.find(i => i.id === left.imageId));\n const rightDicom = Object.values(views).find(d => d.images.find(i => i.id === right.imageId));\n item.views = {};\n if (leftDicom) {\n const { imageLaterality, viewPosition, images, dicomImageType } = leftDicom;\n const imageIndex = images.findIndex(i => i.id === left.imageId);\n const dataType = dicomTypes[dicomImageType];\n const name = `${imageLaterality}${viewPosition}`;\n item.views[`${dataType}_${name}_left`] = {\n ...stepsProperties[name],\n imageIndex,\n dataType: dataType,\n };\n }\n if (rightDicom) {\n const { imageLaterality, viewPosition, images, dicomImageType } = rightDicom;\n const imageIndex = images.findIndex(i => i.id === right.imageId);\n const dataType = dicomTypes[dicomImageType];\n const name = `${imageLaterality}${viewPosition}`;\n item.views[`${dataType}_${name}_right`] = {\n ...stepsProperties[name],\n imageIndex,\n dataType: dataType,\n };\n }\n } else {\n item.views = {\n image: step.imageUrl,\n };\n }\n return item;\n });\n const cleaned = result.filter(\n i =>\n (i.stepType === 'image' && i.imageUrl) ||\n (i.stepType === 'case' && Object.keys(i.views).length),\n );\n\n return cleaned;\n};\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React, { useEffect, useRef, useState } from 'react';\n\nimport { getDefaultType } from '../../../configs';\nimport CanvasView from '../../CanvasView';\nimport ControlArea from '../../ControlArea';\nimport { answerTools } from '../configs';\nimport { createArrayWithIncrementingNumbers } from 'utils/createHelpers';\nimport { LeftArrow, RightArrow } from 'shared/components/Icons';\nimport { IconCase, IconDicomSmall } from 'shared/components/Icons';\n\nimport step1Icon from 'assets/answers/step1.png';\nimport step2Icon from 'assets/answers/step2.png';\nimport step3Icon from 'assets/answers/step3.png';\n\nconst mock = [step1Icon, step2Icon, step3Icon];\n\nconst AnswerViewer = ({ data, mergedSteps, activeStep, setActiveStep }) => {\n const [activeTool, setActiveTool] = useState({});\n const [fullScreen, setFullScreen] = useState(!!document.fullscreenElement);\n const [isScrollAtStart, setIsScrollAtStart] = useState(true);\n const [isScrollAtEnd, setIsScrollAtEnd] = useState(false);\n\n const scrollContainerRef = useRef(null);\n\n const stepsArr = createArrayWithIncrementingNumbers(mergedSteps?.length);\n\n const isListOverThree = stepsArr?.length > 3;\n\n const fullScreenChange = () => {\n document.addEventListener('fullscreenchange', e => {\n setFullScreen(!!document.fullscreenElement);\n });\n };\n\n const scrollLeft = () => {\n if (scrollContainerRef.current) {\n scrollContainerRef.current.scrollTo({\n left: scrollContainerRef.current.scrollLeft - 400,\n behavior: 'smooth',\n });\n }\n };\n\n const scrollRight = () => {\n if (scrollContainerRef.current) {\n scrollContainerRef.current.scrollTo({\n left: scrollContainerRef.current.scrollLeft + 400,\n behavior: 'smooth',\n });\n }\n };\n\n useEffect(() => {\n fullScreenChange();\n //eslint-disable-next-line\n }, []);\n\n if (!mergedSteps || !mergedSteps[activeStep]) {\n return (\n \n No answers frame provided for this case\n
\n );\n }\n\n const activeStepData = mergedSteps[activeStep];\n\n const activeStepIsImage = activeStepData && activeStepData.stepType === 'image';\n const stepViews = mergedSteps[activeStep].views;\n const stepViewsData = Object.keys(stepViews);\n const viewsCount = Object.values(stepViews).length;\n\n const viewSizesForced = () => {\n return {\n width: (window.innerWidth - (fullScreen ? 548 : 240 + 530)) / viewsCount,\n height: window.innerHeight - 290,\n };\n };\n\n const getImage = index => {\n try {\n const step = mergedSteps[index];\n const { key, dataType, imageIndex = 0 } = step.views[Object.keys(step.views)[0]];\n const dicomTypeData = {\n tomo: data[`${key}_TOMO`],\n '2D': data[`${key}_2D`],\n '3DQ': data[`${key}_3DQ`],\n };\n\n const control = getDefaultType(dataType, dicomTypeData);\n const dicomData = dicomTypeData[control];\n return step.stepType === 'image' ? step.views.image : dicomData.images[imageIndex].url;\n } catch (err) {\n return mock[index];\n }\n };\n\n const getType = index => {\n try {\n const step = mergedSteps[index];\n return step.stepType;\n } catch (err) {\n return mock[index];\n }\n };\n\n const handleScroll = event => {\n const { scrollWidth, scrollLeft, clientWidth } = event.target;\n const isAtStart = scrollLeft === 0;\n const isAtEnd = scrollLeft + clientWidth === scrollWidth;\n setIsScrollAtStart(isAtStart);\n setIsScrollAtEnd(isAtEnd);\n };\n\n return (\n \n
\n
setActiveTool(tool)}\n tools={answerTools}\n disabled={activeStepIsImage}\n />\n \n {stepViewsData.map((key, index) => {\n const item = stepViews[key];\n if (activeStepIsImage) {\n return (\n
\n
;\n
\n );\n }\n return (\n
\n setActiveTool(tool)}\n fullScreen={fullScreen}\n viewsCount={viewsCount}\n viewSizesForced={viewSizesForced}\n currentImageIndex={item.imageIndex}\n />\n
\n );\n })}\n
\n \n
\n {isListOverThree && (\n
\n \n
\n )}\n {stepsArr.map(index => {\n return (\n
setActiveStep(index)}\n className={`position-relative pointer flex-fill ${\n activeStep === index ? 'active' : ''\n }`}\n >\n
\n {\n
\n {getType(index) === 'case' ? (\n \n ) : (\n \n )}\n
\n }\n
\n
\n
\n );\n })}\n {isListOverThree && (\n
\n \n
\n )}\n
\n
\n \n
\n );\n};\n\nexport default AnswerViewer;\n","export function createArrayWithIncrementingNumbers(length) {\n return Array(length)\n .fill(null)\n .map((_, idx) => idx);\n}\n","import React from 'react';\n\nconst DisabledPart = () => {\n return (\n \n You must first review all steps of the hanging protocol as indicated below\n
\n );\n};\n\nexport default DisabledPart;\n","import React, { useEffect, useState } from 'react';\nimport { useSnackbar } from 'notistack';\nimport { useDispatch } from 'react-redux';\nimport { getCourses } from 'app/Main/routes/Courses/actions';\nimport InformationPart from './components/InformationPart';\nimport AnswerViewer from './components/AnswerViewer';\nimport DisabledPart from './components/DisabledPart';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport Loading from 'shared/components/Loading';\nimport { mergeSteps } from './configs';\n\nconst ExplanationModal = ({ isDisabled, updateStep, caseData, caseId, caseViews }) => {\n const { enqueueSnackbar } = useSnackbar();\n const dispatch = useDispatch();\n const [fetch, setFetch] = useState(false);\n const [data, setData] = useState();\n const [activeStep, setActiveStep] = useState(0);\n\n const sendUpdate = async () => {\n await updateStep(10);\n dispatch(getCourses());\n };\n\n const getAnswersData = async () => {\n try {\n setFetch(true);\n const { data } = await Api.get(`/cases/getanswers/${caseId}`);\n data.data.mergedSteps = mergeSteps(caseViews, data.data.answers);\n setData(data.data);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetch(false);\n }\n };\n\n useEffect(() => {\n if (!isDisabled) sendUpdate();\n getAnswersData();\n //eslint-disable-next-line\n }, []);\n\n if (isDisabled) return ;\n if (fetch) return ;\n\n if (!data)\n return (\n No Answers Provided for this lesson
\n );\n\n return (\n \n );\n};\n\nexport default ExplanationModal;\n","import React from 'react';\nimport CaseLeftIcon from 'assets/arrows/caseLeft.svg';\nimport CaseRightIcon from 'assets/arrows/caseRight.svg';\n\nconst Switcher = ({ name, previews, next, disabled, current, allCount, highlightNext }) => {\n return (\n \n
\n \n \n
\n {name} {current + 1} of {allCount}\n
\n
allCount || disabled}\n >\n \n \n
\n );\n};\n\nexport default Switcher;\n","import React, { useEffect } from 'react';\nimport Switcher from './Switcher';\nimport { IconAbout } from 'shared/components/Icons';\nimport checkIcon from 'assets/arrows/3p.svg';\nimport StepLeftIcon from 'assets/arrows/stepLeft.svg';\nimport StepRightIcon from 'assets/arrows/stepRight.svg';\n\nconst StepsArea = ({\n reduxLesson,\n steps,\n completedSteps,\n activeCase,\n activeStep,\n changeActiveCase,\n changeActiveStep,\n setActiveStep,\n onHelpModalOpen,\n}) => {\n const isLastCase = activeCase === reduxLesson.caseIds.length - 1;\n const isLastStep = activeStep === steps.length - 1;\n const highlightNextCase = isLastStep && !isLastCase;\n\n const handleStepChange = bool => {\n setActiveStep(current => {\n const disabledPrev = current - 1 < 0;\n const disabledNext = Number(current) + 2 > steps.length;\n if ((bool && !disabledNext) || (!bool && !disabledPrev)) {\n changeActiveStep(current - (bool ? -1 : 1));\n }\n if (bool && disabledNext && !isLastCase) {\n handleCaseChange(true);\n }\n return current;\n });\n };\n\n const handleCaseChange = bool => {\n changeActiveCase(activeCase - (bool ? -1 : 1));\n changeActiveStep(0);\n };\n\n const disabledPrev = () => {\n return activeStep - 1 < 0;\n };\n\n // const goToFeedback = () => {\n // history.push(getFeedbackUrl(reduxCourse));\n // };\n\n const handleUserKeyPress = event => {\n const { keyCode } = event;\n if (keyCode === 37) handleStepChange(false);\n if (keyCode === 39) handleStepChange(true);\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleUserKeyPress);\n return () => {\n window.removeEventListener('keydown', handleUserKeyPress);\n };\n //eslint-disable-next-line\n }, []);\n\n return (\n \n
\n onHelpModalOpen(true)}>\n \n Help\n \n
\n
\n
\n \n \n {steps.map((item, index) => {\n const isActive = completedSteps.includes(index);\n const isExplanator = item.name === 'explanator';\n // const isHeatmap = item.name === 'heatmap';\n const isNeedToDone =\n activeStep >= 9 &&\n completedSteps.length < 10 &&\n !isActive &&\n activeStep !== index &&\n !isExplanator;\n return (\n
changeActiveStep(index)}\n >\n {isActive && }\n {item.icon && (\n \n )}\n \n );\n })}\n {/*
goToFeedback()}>\n \n */}\n
steps.length}\n >\n \n \n
\n
\n
\n );\n};\n\nexport default StepsArea;\n","const findPosition = oElement => {\n if (typeof oElement.offsetParent != 'undefined') {\n for (var posX = 0, posY = 0; oElement; oElement = oElement.offsetParent) {\n posX += oElement.offsetLeft;\n posY += oElement.offsetTop;\n }\n return [posX, posY];\n } else {\n return [oElement.x, oElement.y];\n }\n};\n\nexport const getCoordinates = (e, el) => {\n let PosX = 0;\n let PosY = 0;\n let ImgPos;\n ImgPos = findPosition(el);\n if (!e) e = window.event;\n if (e.pageX || e.pageY) {\n PosX = e.pageX;\n PosY = e.pageY;\n } else if (e.clientX || e.clientY) {\n PosX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;\n PosY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;\n }\n PosX -= ImgPos[0];\n PosY -= ImgPos[1];\n return { x: PosX, y: PosY };\n};\n","import React, { useEffect, useRef, useState } from 'react';\nimport { useSnackbar } from 'notistack';\nimport { getCoordinates } from './utils';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport Loading from 'shared/components/Loading';\n\nconst getCirclePostions = (pos, imgEl) => {\n if (!pos || !imgEl || !imgEl.current) return {};\n const { naturalWidth, naturalHeight, clientWidth, clientHeight } = imgEl.current;\n const widthScale = pos.x / naturalWidth;\n const heightScale = pos.y / naturalHeight;\n const res = {\n left: widthScale * clientWidth,\n top: heightScale * clientHeight,\n };\n return res;\n};\n\nconst HeatmapModal = ({ caseId, lessonId, userLessonId, updateStep, isDisabled, isOpened }) => {\n const { enqueueSnackbar } = useSnackbar();\n const [positions, setPositions] = useState();\n const [fetch, setFetching] = useState();\n const [dicom, setDicom] = useState();\n const imgEl = useRef();\n\n const selectText = positions\n ? 'Thank you, your selection is submitted.'\n : 'Please select by one mouse click a most probable calcification you may notice.';\n\n const onMouseDownOnImage = async e => {\n if (positions) return;\n try {\n const cords = getCoordinates(e, imgEl.current);\n const { naturalWidth, naturalHeight, clientWidth, clientHeight } = imgEl.current;\n const widthScale = cords.x / clientWidth;\n const heightScale = cords.y / clientHeight;\n const pos = {\n x: widthScale * naturalWidth,\n y: heightScale * naturalHeight,\n };\n const { caseId, dicomId, imageId } = dicom;\n await Api.post(`/dicom/saveheatmapdata`, { ...pos, userLessonId, caseId, dicomId, imageId });\n setPositions(pos);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const getHeatMapImage = async () => {\n try {\n setFetching(true);\n const { data } = await Api.get(`/cases/getheatmaps/${lessonId}/${caseId}`);\n const dicom = data.data && data.data[0];\n if (!dicom) return;\n const points = await Api.get(`/dicom/lessonheatmapdata/${userLessonId}/${caseId}`);\n const point = points.data.data && points.data.data[0];\n if (!isOpened) await updateStep(11);\n setDicom(dicom);\n if (point) {\n setTimeout(setPositions, 500, point);\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetching(false);\n }\n };\n\n useEffect(() => {\n getHeatMapImage();\n //eslint-disable-next-line\n }, []);\n\n if (isDisabled)\n return (\n \n You must first review all steps of the hanging protocol as indicated below\n
\n );\n\n if (fetch) return ;\n\n if (!dicom || isOpened) {\n return (\n \n This step is not required for this case\n
\n );\n }\n\n return (\n \n
\n
Calcification Selection \n \n
\n
{selectText} \n
\n
\n {positions &&
}\n
\n
\n
\n );\n};\n\nexport default HeatmapModal;\n","import React, { useState, useEffect } from 'react';\nimport { useSelector, useDispatch } from 'react-redux';\nimport HelpModal from './components/HelpModal/';\nimport QuestionaryModal from './components/QuestionaryModal/';\nimport ExplanationModal from './components/ExplanationModal/';\nimport ControlArea from './components/ControlArea';\nimport StepsArea from './components/StepsArea';\nimport CanvasView from './components/CanvasView';\nimport { steps as initialSteps, tools } from './configs';\n\nimport './style.scss';\nimport { Api } from 'utils/connectors';\nimport { generateViewerData } from './utils';\nimport Loading from 'shared/components/Loading';\nimport { onCourseUpdate } from '../Courses/actions';\nimport HeatmapModal from './components/HeatmapModal';\nimport { removeFromStore } from 'utils/storeHelpers';\nimport { getError } from 'utils/appHelpers';\nimport { useSnackbar } from 'notistack';\n\nconst Viewer = ({ match, history }) => {\n const { enqueueSnackbar } = useSnackbar();\n const courses = useSelector(store => store.courses);\n const viewerOptions = useSelector(state => state.viewerOptions);\n const dispatch = useDispatch();\n const { courseId, lessonId } = match.params;\n\n const [activeTool, setActiveTool] = useState({});\n const [fullScreen, setFullScreen] = useState(false);\n\n const [fetch, setFetch] = useState();\n const [course, setCourse] = useState();\n const [activeCase, setActiveCase] = useState(0);\n const [activeStep, setActiveStep] = useState(0);\n\n const [openHelpModal, setOpenHelpModal] = useState(false);\n\n const reduxCourse = courses.find(item => item.id === Number(courseId));\n const reduxLesson =\n reduxCourse && reduxCourse.lessons.find(lesson => lesson.id === Number(lessonId));\n\n const activeCaseId = reduxLesson?.caseIds?.[activeCase]?.caseId || 0;\n const activeCaseEpisodes =\n reduxLesson?.episodes?.length && reduxLesson.episodes.filter(ep => ep.caseId === activeCaseId);\n const sortedActiveCaseEpisodes =\n activeCaseEpisodes &&\n [...activeCaseEpisodes].sort((ep1, ep2) => {\n return ep1.orderNum - ep2.orderNum;\n });\n\n const fullScreenChange = () => {\n document.addEventListener('fullscreenchange', e => {\n setFullScreen(!!document.fullscreenElement);\n });\n };\n\n const handelActiveToolChange = tool => {\n setActiveTool(tool);\n if (tool && tool.type === 'findingReset') resetFinding();\n };\n\n const getCases = async () => {\n if (!reduxLesson || !reduxLesson.caseIds || !reduxLesson.caseIds.length) return;\n const { caseId, caseUniqueId } = reduxLesson.caseIds[activeCase];\n const endpoint = `/cases/getcasebyid/${caseUniqueId || caseId}?lut=1&dicomType=all`;\n const { data } = await Api.get(endpoint);\n const cases = generateViewerData(data.data, reduxLesson);\n setCourse({\n title: reduxLesson.name,\n lesson: reduxLesson,\n cases,\n });\n // Update first step\n if (!cases[0].completed_steps.includes(0)) {\n updateDicomStepData(0);\n }\n };\n\n const onCaseChange = async value => {\n if (!course.cases[value]) {\n setFetch(true);\n const { caseId, caseUniqueId } = reduxLesson.caseIds[value];\n const endpoint = `/cases/getcasebyid/${caseUniqueId || caseId}?lut=1&dicomType=all`;\n const { data } = await Api.get(endpoint);\n const caseData = generateViewerData(data.data, reduxLesson);\n const cases = [...course.cases, ...caseData];\n if (!cases[value].completed_steps.includes(0)) {\n updateDicomStepData(0, caseId);\n cases[value].completed_steps.push(0);\n }\n await setCourse({ ...course, cases });\n await setActiveCase(value);\n setFetch(false);\n } else {\n setActiveCase(value);\n }\n };\n\n const updateLessonsEpisode = index => {\n const tempCourse = { ...reduxCourse };\n const lessonIndex = tempCourse.lessons.findIndex(item => item.id === Number(lessonId));\n const tempLesson = tempCourse.lessons[lessonIndex];\n const tempEpisode = tempLesson.episodes.find(\n ep => ep.id === sortedActiveCaseEpisodes[index].id,\n );\n if (tempEpisode) {\n tempEpisode.status = 1;\n dispatch(onCourseUpdate(tempCourse));\n }\n };\n\n const updateDicomStepData = async (step, id) => {\n const caseId = id || reduxLesson.caseIds[activeCase].caseId;\n const body = {\n lessonId,\n completed: 1,\n step: step + 1,\n caseId: Number(caseId),\n courseId: reduxCourse?.id,\n };\n await Api.post('/courses/updateuserlesson', body);\n updateLessonsEpisode(step);\n };\n\n const updateAndAddCompletedStep = async step => {\n await updateDicomStepData(step);\n completedSteps.push(step);\n };\n\n const stepChangeHandler = async step => {\n const isModal = step === 9 || step === 10 || step === 11;\n if (isModal) setActiveTool({});\n if (!completedSteps.includes(step) && !isModal) {\n await updateAndAddCompletedStep(step);\n }\n setActiveStep(step);\n };\n\n const updateFinding = async body => {\n await Api.post('/dicom/savedicomvectordata', body);\n };\n\n const resetFinding = async () => {\n try {\n const body = {\n userLessonId: reduxLesson.userLessonId,\n caseId: Number(reduxLesson.caseIds[activeCase].caseId),\n };\n await Api.delete('/dicom/dicomvectordata', {\n data: body,\n });\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const lessonStartLog = () => {\n Api.post('/courses/lesson/start', {\n courseId: reduxCourse.id,\n lessonId: reduxLesson.id,\n });\n };\n\n useEffect(() => {\n fullScreenChange();\n getCases();\n removeFromStore('viewerToolTip');\n lessonStartLog();\n //eslint-disable-next-line\n }, []);\n\n if (!course || fetch) return ;\n\n let steps = [...initialSteps];\n\n const selectedCase = reduxLesson.caseIds[activeCase];\n if (!!reduxCourse.isOpened || !selectedCase.hasHeatmap) {\n // remove questionary and heatmaps\n const excludeNames = reduxCourse.isOpened\n ? ['question', 'heatmap']\n : !selectedCase.hasHeatmap\n ? ['heatmap']\n : [];\n steps = steps.filter(item => !excludeNames.includes(item.name));\n }\n\n const isModal = steps[activeStep].type === 'modal';\n const caseViews = course.cases[activeCase].views;\n const completedSteps = course.cases[activeCase].completed_steps;\n const stepViews = steps[activeStep].views;\n const stepViewsData = Object.values(stepViews);\n const viewsCount = Object.values(stepViews).length;\n\n return (\n \n
\n
\n {!isModal &&\n stepViewsData.map((item, index) => (\n \n ))}\n {isModal && (\n <>\n {steps[activeStep].name === 'question' && (\n \n )}\n {steps[activeStep].name === 'explanator' && (\n \n )}\n {steps[activeStep].name === 'heatmap' && (\n \n )}\n >\n )}\n
\n
\n {openHelpModal &&
setOpenHelpModal(false)} />}\n \n );\n};\n\nexport default Viewer;\n","import React, { useRef, useEffect, useState } from 'react';\nimport * as cornerstone from 'cornerstone-core';\nimport cornerstoneTools from 'cornerstone-tools/dist/cornerstoneTools';\nimport { HOST } from 'configs';\nimport ArrowTool from '../Viewer/plugins/ArrowTool';\n\nconst original = `${\n HOST.API.CONTENT\n}/dicom/2018_CHDI2D_107_BTOJPEGLossless_MG_R_CC_TomoHD_24X29_20180331_/img_1_lut.jpg`;\n\nconst TestViewer = () => {\n const [imageUrl, setImageUrl] = useState(original);\n\n const el1 = useRef(null);\n\n const onWindowResize = () => {\n cornerstone.resize(el1.current);\n // console.log(cornerstoneTools.getElementToolStateManager(el1.current));\n };\n\n const loadImage = async (element, url) => {\n const image = await cornerstone.loadAndCacheImage(url || imageUrl);\n const viewportOptions = { image };\n await cornerstone.displayImage(element, image, viewportOptions);\n };\n\n const initElement = async element => {\n await cornerstone.enable(element);\n await cornerstone.resize(element);\n window.addEventListener('resize', onWindowResize);\n };\n\n const initData = async (element, url) => {\n await loadImage(element, url);\n await cornerstone.fitToWindow(element);\n await cornerstone.resize(element);\n\n // cornerstoneTools.addToolForElement(element, cornerstoneTools.ArrowAnnotateTool, {\n // configuration: { getTextCallback: () => {} },\n // });\n // cornerstoneTools.setToolActive('ArrowAnnotate', { mouseButtonMask: 1 });\n // console.log(cornerstoneTools);\n // console.dir(cornerstoneTools.ArrowAnnotateTool);\n\n const data = [\n {\n x: 2633.665306122448,\n y: 1905.89387755102,\n },\n {\n x: 1864.620408163264,\n y: 2474.3183673469384,\n },\n ];\n\n cornerstoneTools.addToolForElement(el1.current, ArrowTool, { data });\n cornerstoneTools.setToolActive('ArrowTool', { mouseButtonMask: 1 });\n };\n\n const onImageChange = e => {\n e.preventDefault();\n cornerstoneTools.addTool(cornerstoneTools.PanTool);\n cornerstoneTools.setToolActive('Pan', { mouseButtonMask: 1 });\n // initData(el1.current);\n };\n\n useEffect(() => {\n initElement(el1.current);\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n initData(el1.current);\n //eslint-disable-next-line\n }, []);\n\n return (\n \n
\n
\n \n Image \n setImageUrl(target.value)}\n value={imageUrl}\n />\n
\n \n change image\n \n \n
\n
\n
\n );\n};\n\nexport default TestViewer;\n","import React from 'react';\nimport * as IconList from 'shared/components/Icons';\n\nconst Icons = () => {\n const keys = Object.keys(IconList);\n const bindKey = key => `<${key} />`;\n\n const copyToClipboard = str => {\n const el = document.createElement('textarea');\n el.value = `<${str} />`;\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n document.body.removeChild(el);\n };\n\n return (\n \n );\n};\n\nexport default Icons;\n","import React from 'react';\n\nconst ViewAlert = ({ data, optimalSettings, minimalSettings }) => {\n const { innerWidth, innerHeight } = data;\n return (\n <>\n {innerWidth >= optimalSettings.width && innerHeight >= optimalSettings.height ? (\n Passed
\n ) : innerWidth >= minimalSettings.width && innerHeight >= minimalSettings.height ? (\n \n The optimal resolutions is {optimalSettings.width}x{optimalSettings.height}.\n
\n ) : (\n \n The window size is too small, for optimal image quality.\n \n Hint: Maximize the browser window.\n
\n )}\n >\n );\n};\n\nexport default ViewAlert;\n","import React, { memo } from 'react';\nimport { useDispatch } from 'react-redux';\nimport { setDiagnosticData } from '../../actions';\nimport ViewAlert from './ViewAlert';\n\nconst settings = {\n optimal: { width: 1024, height: 768 },\n minimal: { width: 1024, height: 600 },\n};\n\nconst ResolutionTest = () => {\n const dispatch = useDispatch();\n const { innerWidth, innerHeight, screen } = window;\n const { width, height } = screen;\n const result = {\n screen: `${width}x${height}`,\n window: `${innerWidth}x${innerHeight}`,\n success: innerWidth >= settings.optimal.width && innerHeight >= settings.optimal.height,\n };\n\n dispatch(setDiagnosticData({ resolution: result }));\n\n return (\n <>\n \n
\n Screen resolution: \n ({`${width}x${height}`}) \n
\n
\n Window resolution: \n ({`${innerWidth}x${innerHeight}`}) \n
\n
\n \n >\n );\n};\n\nexport default memo(ResolutionTest);\n","import React from 'react';\n\nconst SpinnerLoading = ({ className = 'spinner-border-sm' }) => {\n return (\n \n Loading... \n \n );\n};\n\nexport default SpinnerLoading;\n","import React, { memo } from 'react';\n\nconst Feedback = memo(\n ({\n data,\n resultKey,\n optimalSettings,\n minimalSettings,\n successText,\n warningText,\n failText,\n aborted,\n }) => {\n const feedback = {\n loading: {\n text: 'Testing... Please wait!',\n className: '',\n },\n success: {\n text: 'Passed',\n className: ' alert-success',\n },\n warning: {\n text: failText,\n className: ' alert-warning-light',\n },\n fail: {\n text: failText,\n className: ' alert-warning',\n },\n abort: {\n text: 'Bad connection detected! Aborted.',\n className: ' alert-danger',\n },\n };\n const feedbackKey = aborted\n ? 'abort'\n : !data\n ? 'loading'\n : data[resultKey] <= optimalSettings\n ? 'success'\n : data[resultKey] <= minimalSettings\n ? 'warning'\n : 'fail';\n return (\n \n
{feedback[feedbackKey].text}
\n
\n );\n },\n);\n\nexport default Feedback;\n","export const MAX_WAIT_TIME = 7000;\n","import React from 'react';\nimport { MAX_WAIT_TIME } from '../../constants';\n\nconst ViewResult = ({ data }) => {\n const { min, max, avg } = data;\n const localMin = min > MAX_WAIT_TIME ? MAX_WAIT_TIME : min?.toFixed(0);\n const localMax = max > MAX_WAIT_TIME ? MAX_WAIT_TIME : max?.toFixed(0);\n const localAvg = avg > MAX_WAIT_TIME ? MAX_WAIT_TIME : avg?.toFixed(0);\n return (\n <>\n {!min && !max && !avg ? (\n No Response \n ) : (\n \n (Average: {avg > MAX_WAIT_TIME && `>`} {localAvg} ms, Min:{' '}\n {min > MAX_WAIT_TIME && `>`} {localMin} ms, Max: {max > MAX_WAIT_TIME && `>`}{' '}\n {localMax} ms)\n \n )}\n >\n );\n};\n\nexport default ViewResult;\n","import React, { useEffect, useState, memo } from 'react';\nimport { Api } from 'utils/connectors';\nimport { getError } from 'utils/appHelpers';\nimport { useSnackbar } from 'notistack';\nimport { useDispatch } from 'react-redux';\nimport { setDiagnosticData } from '../../actions';\nimport SpinnerLoading from './../SpinnerLoading';\nimport Feedback from './../Feedback';\nimport ViewResult from './ViewResult';\nimport { MAX_WAIT_TIME } from '../../constants';\n\nconst threshold = 3; // This is the minimal threshold to count MIN, MAX and AVG\nconst repeatLimit = 2;\nconst limit = threshold + repeatLimit;\n\n// Latency\nconst settings = {\n optimal: 5000,\n minimal: 5000,\n timeout: MAX_WAIT_TIME,\n};\n\nconst requestUrl = '/common/test-latency';\n\nconst LatencyTest = () => {\n const { enqueueSnackbar } = useSnackbar();\n const dispatch = useDispatch();\n const [result, setResult] = useState(null);\n const [aborted, setAborted] = useState(false);\n\n const checkNetworkConnection = () => {\n if (result) return;\n\n const promises = [];\n\n for (let i = 1; i <= limit; i++) {\n promises.push(\n new Promise((resolve, reject) => {\n const start = performance.now();\n const timeoutId = setTimeout(() => {\n resolve(settings.timeout);\n }, settings.timeout);\n\n Api.get(requestUrl, { timeout: settings.timeout })\n .then(res => {\n clearTimeout(timeoutId);\n const millis = performance.now() - start;\n resolve(Math.round(millis % 60000));\n })\n .catch(err => {\n clearTimeout(timeoutId);\n reject(err);\n });\n }),\n );\n }\n\n Promise.allSettled(promises)\n .then(results => {\n const successfulResults = results.filter(r => r.status === 'fulfilled').map(r => r.value);\n const allTimedOut = successfulResults.every(res => res === MAX_WAIT_TIME);\n\n if (allTimedOut) throw new Error('All latency tests failed or timed out.');\n\n if (successfulResults.length > 0 && !allTimedOut) {\n // Process successful results\n let sorted = [...successfulResults];\n sorted.sort((a, b) => a - b);\n const avg = Math.round(sorted.reduce((a, b) => a + b) / sorted.length);\n const min = sorted[0];\n const max = sorted[sorted.length - 1];\n dispatch(\n setDiagnosticData({ latency: { avg, min, max, success: avg <= settings.optimal } }),\n );\n setResult({ avg, min, max, success: avg <= settings.optimal });\n }\n })\n .catch(err => {\n // Handle errors if all promises are rejected\n dispatch(setDiagnosticData({ failed: true, latencyTimedOut: true }));\n setAborted(true);\n setResult({ success: false });\n dispatch(setDiagnosticData({ latency: { success: false } }));\n enqueueSnackbar(getError(err), { variant: 'error' });\n });\n };\n\n useEffect(() => {\n checkNetworkConnection();\n }, [result]);\n\n return (\n <>\n \n
\n Latency: \n \n {result ? : }\n \n
\n
\n \n The latency is too high for optimal viewing performance.\n \n Hint: Try other internet connection.\n >\n }\n aborted={aborted}\n />\n >\n );\n};\n\nexport default memo(LatencyTest);\n","import React from 'react';\n\nconst ViewResult = ({ data, stoppedEarly, maxWaitTime }) => {\n const { received, duration } = data;\n const showingDuration = stoppedEarly ? Math.ceil(maxWaitTime) : duration?.toFixed(1);\n return (\n \n received {received} MB {duration > maxWaitTime ? '>' : 'in'} {showingDuration} s\n \n );\n};\n\nexport default ViewResult;\n","import React, { useState, useEffect, memo } from 'react';\n// import { useSelector } from 'react-redux';\nimport { useDispatch } from 'react-redux';\nimport { setDiagnosticData } from '../../actions';\nimport SpinnerLoading from './../SpinnerLoading';\nimport Feedback from './../Feedback';\nimport ViewResult from './ViewResult';\nimport { HOST } from 'configs';\n\nconst imageFile = `${HOST.API.CONTENT}/test/bw-test1.jpg`;\nconst downloadSize = 5083725; //bytes;\n\n// Bandwidth duration\nconst settings = {\n optimal: 8,\n minimal: 5,\n timeout: 35000,\n};\n\nconst minDownloadSpeed = settings.minimal / 8;\nconst minimalBandwidthBytesPerSecond = minDownloadSpeed * 1024 * 1024; // Convert Mbps to bytes per second\n\n// Calculate max wait time (in seconds)\nconst maxBandwidthWaitTime = downloadSize / minimalBandwidthBytesPerSecond;\n\nsettings.minimal = maxBandwidthWaitTime;\nsettings.optimal = (maxBandwidthWaitTime * 2) / 3;\n\nconst BandwidthTest = ({ stoppedEarly, setStoppedEarly }) => {\n // Check wether the bandwith data is exists in the redux store\n // const testCompleted = useSelector(state => state.diagnosticData.bandwidth);\n const dispatch = useDispatch();\n const [result, setResult] = useState(null);\n const [aborted, setAborted] = useState(false);\n\n const checkBandwidth = () => {\n if (aborted) return;\n\n let startTime, intervalId;\n const download = new Image();\n\n download.onload = function() {\n clearInterval(intervalId); // Clear the interval when the image loads successfully\n showResults((new Date().getTime() - startTime) / 1000);\n };\n\n download.onerror = function(err, msg) {\n clearInterval(intervalId); // Clear the interval in case of an error\n setAborted(true);\n };\n\n const checkDuration = () => {\n const currentDuration = (new Date().getTime() - startTime) / 1000;\n if (currentDuration > maxBandwidthWaitTime) {\n clearInterval(intervalId); // Stop the interval check\n download.src = ''; // Stop downloading the image\n showResults(currentDuration, true); // Pass true to indicate the test was stopped\n }\n };\n\n startTime = new Date().getTime();\n const cacheBuster = '?nnn=' + startTime;\n download.src = imageFile + cacheBuster;\n\n intervalId = setInterval(checkDuration, 100); // Start the interval check loop\n\n function showResults(duration, stoppedEarly = false) {\n const result = {\n received: 5.2,\n duration,\n success: duration <= settings.optimal,\n failed: duration > settings.minimal,\n };\n setStoppedEarly(stoppedEarly);\n setResult(result);\n }\n };\n\n useEffect(() => {\n if (result) {\n dispatch(setDiagnosticData({ bandwidth: result }));\n }\n }, [dispatch, result]);\n\n useEffect(() => {\n if (aborted && !stoppedEarly) {\n dispatch(setDiagnosticData({ failed: true }));\n }\n }, [aborted, dispatch]);\n\n useEffect(() => {\n checkBandwidth();\n //eslint-disable-next-line\n }, []);\n\n return (\n <>\n \n
\n Bandwidth: \n \n {aborted && !stoppedEarly ? (\n 'No Response.'\n ) : result ? (\n \n ) : (\n \n )}\n \n
\n
\n \n The bandwidth is too low for optimal viewing performance.\n \n Hint: Try other internet connection.\n >\n }\n aborted={aborted}\n />\n >\n );\n};\n\nexport default memo(BandwidthTest);\n","import React, { useState, useEffect, useCallback } from 'react';\nimport { useSnackbar } from 'notistack';\nimport { useSelector, useDispatch } from 'react-redux';\nimport { setDiagnosticData } from './actions';\nimport { Api } from 'utils/connectors';\nimport { getError } from 'utils/appHelpers';\nimport { IconRight } from 'shared/components/Icons';\nimport { ResolutionTest, LatencyTest, BandwidthTest } from './components';\nimport Modal from 'react-modal';\nimport SpinnerLoading from './components/SpinnerLoading';\nimport Feedback from './components/Feedback';\nimport { saveToStore } from 'utils/storeHelpers';\n\nnavigator.userAgentVersion = (function() {\n var ua = navigator.userAgent;\n var tem;\n var M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\\/))\\/?\\s*(\\d+)/i) || [];\n if (/trident/i.test(M[1])) {\n tem = /\\brv[ :]+(\\d+)/g.exec(ua) || [];\n return 'IE ' + (tem[1] || '');\n }\n if (M[1] === 'Chrome') {\n tem = ua.match(/\\b(OPR|Edge)\\/(\\d+)/);\n if (tem != null)\n return tem\n .slice(1)\n .join(' ')\n .replace('OPR', 'Opera');\n }\n M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];\n if ((tem = ua.match(/version\\/(\\d+)/i)) != null) M.splice(1, 1, tem[1]);\n return M.join(' ');\n})();\n\nnavigator.userOsInfo = (function() {\n var OSName = 'Unknown';\n if (window.navigator.userAgent.indexOf('Windows NT 10.0') !== -1) OSName = 'Windows 10';\n if (window.navigator.userAgent.indexOf('Windows NT 6.2') !== -1) OSName = 'Windows 8';\n if (window.navigator.userAgent.indexOf('Windows NT 6.1') !== -1) OSName = 'Windows 7';\n if (window.navigator.userAgent.indexOf('Windows NT 6.0') !== -1) OSName = 'Windows Vista';\n if (window.navigator.userAgent.indexOf('Windows NT 5.1') !== -1) OSName = 'Windows XP';\n if (window.navigator.userAgent.indexOf('Windows NT 5.0') !== -1) OSName = 'Windows 2000';\n if (window.navigator.userAgent.indexOf('Mac') !== -1) OSName = 'Mac/OS';\n if (window.navigator.userAgent.indexOf('X11') !== -1) OSName = 'UNIX';\n if (window.navigator.userAgent.indexOf('Linux') !== -1) OSName = 'Linux';\n return OSName;\n})();\n\nlet fetching = false;\n\nconst ConnectionAndDeviceTestModal = ({ isOpen, onFinish }) => {\n const { enqueueSnackbar } = useSnackbar();\n const dispatch = useDispatch();\n const storageData = useSelector(state => state.diagnosticData);\n\n const [finished, setFinished] = useState(false);\n const [requestConfirmation, setRequestConfirmation] = useState(false);\n const [disabled, setDisabled] = useState(false);\n const [stoppedEarly, setStoppedEarly] = useState(false);\n\n const sendConnectionAndTestResults = useCallback(async () => {\n if (fetching) return;\n fetching = true;\n const result = {\n testData: JSON.stringify({\n ...storageData,\n browser: navigator.userAgentVersion,\n os: navigator.userOsInfo,\n }),\n };\n try {\n await Api.post('/common/saveconnectiontest', { ...result });\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n fetching = false;\n }, [enqueueSnackbar, storageData]);\n\n const defineStorageKey = () => {\n saveToStore('connectionAndDeviceTested', true);\n };\n\n const finishAndCloseDialog = () => {\n defineStorageKey();\n dispatch(\n setDiagnosticData({\n resolution: null,\n latency: null,\n bandwidth: null,\n failed: false,\n }),\n );\n onFinish();\n };\n\n const setLoadingWidth = () => {\n const width = storageData.failed\n ? 100\n : (100 / 3) * Object.values(storageData).filter(v => v && v !== true).length; //objects count only\n return `${width}%`;\n };\n\n const setRequirementsCheckbox = () => {\n const { bandwidth, latency, resolution, failed } = storageData;\n const allSuccess =\n bandwidth &&\n bandwidth.success &&\n latency &&\n latency.success &&\n resolution &&\n resolution.success &&\n !failed;\n setDisabled(!allSuccess);\n setRequestConfirmation(!allSuccess);\n };\n\n const manageSuccess = useCallback(() => {\n const { resolution, latency, bandwidth } = storageData;\n const success = resolution.success && latency.success && bandwidth.success;\n setFinished(true);\n sendConnectionAndTestResults();\n if (!success) setRequirementsCheckbox();\n //eslint-disable-next-line\n }, [sendConnectionAndTestResults, storageData]);\n\n const manageFailure = useCallback(() => {\n setRequirementsCheckbox();\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n const { resolution, latency, bandwidth } = storageData;\n if (storageData.failed) manageFailure();\n if (resolution && latency && bandwidth) manageSuccess();\n }, [storageData, manageFailure, manageSuccess]);\n\n return (\n \n \n
Connection Test \n \n \n
\n
\n
\n
\n
\n Connection to server: \n \n {!storageData.latency ? (\n \n ) : storageData.failed && !stoppedEarly ? (\n <>{storageData.latencyTimedOut ? 'Not defined' : 'No'}>\n ) : (\n <>{storageData.latencyTimedOut ? 'Not defined' : 'Yes'}>\n )}\n \n
\n
\n
\n
\n \n {requestConfirmation && (\n
\n setDisabled(!e.target.checked)}\n />\n \n \n I wish to proceed knowing my system may not meet the proper requirements.\n \n \n
\n )}\n {(finished || requestConfirmation) && (\n
\n Continue \n \n \n )}\n
\n
\n \n );\n};\n\nexport default ConnectionAndDeviceTestModal;\n","import React from 'react';\nimport { useLocation } from 'react-router';\nimport BackButton from 'shared/BackButton';\nimport CourseHeaderRightNav from '../CourseView/components/CourseHeaderRightNav';\n\nconst ProgressHeader = ({ course, history }) => {\n const location = useLocation();\n const isFromAccount = location.state && location.state.from === 'account';\n const options = {\n [isFromAccount ? 'noBackRoute' : 'route']: `/courses/${course.id}`,\n };\n\n return (\n \n
\n
\n \n Progress for: \n
\n
{course.name} \n
\n {!isFromAccount &&
}\n
\n );\n};\nexport default ProgressHeader;\n","import React from 'react';\n\nconst ProgressIncorectBar = ({ progress, correctProgress, className = '', isDICOM }) => {\n const hasInccorect =\n correctProgress !== null && correctProgress !== undefined && correctProgress !== 100;\n const incorrect = isDICOM\n ? progress - (progress * correctProgress) / 100\n : progress - (correctProgress || 0);\n const correct = isDICOM\n ? (progress * correctProgress) / 100\n : progress - (hasInccorect ? incorrect : 0);\n const isCompleted = progress >= 100;\n const status =\n !isCompleted && !hasInccorect\n ? 'progress'\n : isCompleted && !hasInccorect\n ? 'completed'\n : 'correct';\n return (\n \n \n {hasInccorect && (\n \n )}\n \n );\n};\n\nexport default ProgressIncorectBar;\n","import React, { useEffect } from 'react';\nimport { connect, useSelector } from 'react-redux';\nimport ProgressHeader from './ProgressHeader';\nimport ProgressIncorectBar from 'shared/components/ProgressIncorectBar';\nimport { getCourses } from './../Courses/actions/index';\n\nconst CourseProgress = ({ match, history, getCourses }) => {\n const courses = useSelector(state => state.courses);\n const course = courses.find(item => item.id === Number(match.params.id));\n\n useEffect(() => {\n getCourses();\n }, [getCourses]);\n\n if (course.isOpened) {\n return (\n \n
\n
\n No Progress is available for this course\n
\n
\n );\n }\n\n return (\n <>\n \n \n
\n {course.lessons &&\n course.lessons.map(lesson => {\n const { id, name, startDate, endDate, progress, correctProgress } = lesson;\n const isCompleted = progress && progress >= 100;\n const isDICOM = lesson.type === 'cases';\n\n return (\n \n \n {name}\n
\n \n
\n \n {startDate ? `Start ${startDate}` : 'Not Started'}\n \n {!!isCompleted && (\n {endDate && `End ${endDate}`} \n )}\n
\n
\n
\n \n {progress}%\n
\n \n );\n })}\n \n
\n
\n
\n
\n
\n \n Correct Answers\n
\n
\n \n Lesson In Progress\n
\n
\n
\n
\n
\n
\n
\n
\n \n Incorrect Answers\n
\n
\n \n Completed Lesson\n
\n
\n
\n
\n
\n
\n >\n );\n};\n\nexport default connect(\n null,\n { getCourses },\n)(CourseProgress);\n","import React from 'react';\nimport BackButton from 'shared/BackButton';\nimport { getEventUrl } from 'utils/appHelpers';\nimport { hasAccess } from 'utils/permissionHelper';\nimport CourseHeaderRightNav from '../CourseView/components/CourseHeaderRightNav';\n\nconst ProgressHeader = ({ course, history, file }) => {\n const path = hasAccess('event_user') ? getEventUrl() : '';\n return (\n \n
\n
\n \n Certificate: \n
\n {file && (\n
\n )}\n
\n
\n
\n );\n};\nexport default ProgressHeader;\n","import React from 'react';\nimport { useSelector } from 'react-redux';\nimport CourseHeader from './CourseHeader';\nimport { Document, Page, pdfjs } from 'react-pdf';\n\nconst pdfVersion = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;\npdfjs.GlobalWorkerOptions.workerSrc = pdfVersion;\n\nconst CourseCertificate = ({ history, match }) => {\n const { id } = match.params;\n const courses = useSelector(store => store.courses);\n const course = courses.find(item => item.id === Number(id));\n const file = course.certificateUrl;\n\n if (!course.isCertificateAvailable) {\n return (\n \n
\n
No Certificate is available for this course
\n
\n );\n }\n\n return (\n \n
\n {file && (\n
\n \n \n )}\n {!file && (\n
\n Your certificate will be available after completion of the course.\n
\n )}\n
\n );\n};\n\nexport default CourseCertificate;\n","import React from 'react';\nimport Slider from 'react-slick';\nimport AuthorBox from 'shared/components/AuthorBox';\n\nconst sliderSettings = {\n speed: 500,\n infinite: true,\n slidesToShow: 4,\n slidesToScroll: 1,\n};\n\nconst AuthorsList = ({ data }) => {\n sliderSettings.slidesToShow = data.length < 4 ? data.length : 4;\n const col = data.length < 4 ? data.length * 3 : 12;\n\n return (\n \n
\n {data.map(item => (\n \n ))}\n \n
\n );\n};\n\nexport default AuthorsList;\n","import { DEFAULT_IMG_URLS } from 'configs/constants';\nimport React, { useState } from 'react';\nimport { bindDuration } from 'utils/appHelpers';\n\nconst LessonsList = ({ data }) => {\n const [activeIndex, setActiveIndex] = useState(0);\n\n const toggleAccordion = index => setActiveIndex(activeIndex === index ? null : index);\n\n if (!data || !data.length) return null;\n\n return (\n \n {data\n .sort((l1, l2) => l1.lessonOrder - l2.lessonOrder)\n .map((item, index) => (\n
\n toggleAccordion(index)}\n role='presentation'\n >\n \n
\n
\n
\n
\n
{item.name} \n
\n
\n duration:{' '}\n {item.duration || item.timeLimit\n ? bindDuration(item.duration || item.timeLimit)\n : 'No Limit'}\n
\n
chapters: {item.episodes.length || 0}
\n
type: {item.type}
\n
\n
\n
\n {item.description}
\n \n {activeIndex === index && (\n \n \n {item.episodes.map(epi => (\n \n {epi.name}\n \n {epi.duration || epi.timeLimit\n ? bindDuration(epi.duration || epi.timeLimit)\n : 'No time limit'}\n \n \n ))}\n \n \n )}\n \n ))}\n
\n );\n};\n\nexport default LessonsList;\n","import React, { useState, useEffect } from 'react';\nimport { connect, useSelector } from 'react-redux';\nimport { confirmAlert } from 'react-confirm-alert';\nimport { useSnackbar } from 'notistack';\n\nimport { COUNTRY_ID_OBJ } from 'app/Auth/routes/Register/configs';\nimport {\n courseSubsStatuses,\n findCourseFirstLesson,\n findCourseFirstLessonAndRedirect,\n getError,\n} from 'utils/appHelpers';\nimport { formatSubscription } from 'utils/formatHelpers';\nimport { Api } from 'utils/connectors';\nimport { getFromStore } from 'utils/storeHelpers';\n\nimport AuthorsList from './components/AuthorsList';\nimport LessonsList from './components/LessonsList';\nimport { getCourses } from '../Courses/actions';\nimport BackButton from 'shared/BackButton';\nimport { IconKey, IconLock } from 'shared/components/Icons';\nimport TagsViewBlock from 'shared/components/TagsViewBlock';\nimport SubscriptionModal from 'shared/components/SubscriptionModal';\nimport ProgressBar from 'shared/components/ProgressBar';\nimport ButtonLoading from 'shared/components/ButtonLoading';\nimport Loading from 'shared/components/Loading';\nimport { API_RESPONSE_STATUS_CODES } from 'configs/constants';\n\nconst subsTypes = {\n 1: 'No Key Required',\n 2: 'Paid',\n 3: (\n <>\n Key Required\n >\n ),\n};\n\nconst LIVE_COURSE_IDS_OBJ = {\n us: [29, 6],\n aus: [32],\n};\n\nconst CourseDetails = ({ history, match, getCourses }) => {\n const { enqueueSnackbar } = useSnackbar();\n const { id, action } = match.params;\n const courses = useSelector(store => store.courses);\n const course = courses.find(item => item.id === Number(id));\n const [modalData, setModalData] = useState(null);\n const [subscription, setSubscription] = useState(null);\n const [fetch, setFetch] = useState(null);\n const [isCheckingCourseAccess, setIsCheckingCourseAccess] = useState(false);\n const [isTestCompleted, setIsTestCompleted] = useState(getFromStore('connectionAndDeviceTested'));\n\n const radiologist8HourCourse =\n courses && courses?.find(course => course?.name?.startsWith('Radiologists: 8-hour'));\n const radiologist8HourCourseId = radiologist8HourCourse?.id;\n\n const profile = useSelector(state => state.account);\n\n const openWarningModal = message => {\n confirmAlert({\n message,\n buttons: [\n {\n label: 'Close',\n },\n ],\n });\n };\n\n const fetchData = async isEnroll => {\n try {\n const subs = course.subscriptionPlans && course.subscriptionPlans[0];\n const { data } = await Api.get(`subscription/getsubscription/${subs.subscriptionId}`);\n setSubscription(formatSubscription(data.data));\n if (isEnroll) {\n findCourseFirstLessonAndRedirect(course, history);\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const checkAccess = async () => {\n try {\n setIsCheckingCourseAccess(true);\n const res = await Api.get(`/courses/check-to-enroll/${id}`);\n if (\n res?.status === API_RESPONSE_STATUS_CODES.Success ||\n res?.status === API_RESPONSE_STATUS_CODES.CreateUpdate\n ) {\n const needToGetCourses = res?.status === API_RESPONSE_STATUS_CODES.CreateUpdate;\n findCourseFirstLessonAndRedirect(course, history, needToGetCourses);\n }\n } catch (err) {\n return;\n } finally {\n setIsCheckingCourseAccess(false);\n }\n };\n\n const onUnSubscribeSubscription = async () => {\n try {\n setFetch(true);\n await Api.post('/subscription/unsubscribe', { subscriptionId: subscription.id });\n await getCourses();\n await fetchData();\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetch(false);\n }\n };\n\n const checkIsTestCompleted = event => {\n const isDone = getFromStore('connectionAndDeviceTested');\n if (isDone) setIsTestCompleted(true);\n else setTimeout(checkIsTestCompleted, 1000);\n };\n\n useEffect(() => {\n if (LIVE_COURSE_IDS_OBJ.aus.includes(Number(id))) {\n if (profile.countryId === COUNTRY_ID_OBJ.us) {\n openWarningModal(\n 'Please note: this course is intended for our friends in Australia and New Zealand only.',\n );\n }\n } else if (LIVE_COURSE_IDS_OBJ.us.includes(Number(id))) {\n if (profile.countryId === COUNTRY_ID_OBJ.aus) {\n openWarningModal(\n 'Please note: there is a specific course intended for our friends in United States.',\n );\n }\n }\n\n if (course) {\n fetchData();\n if (course.userCourse) {\n findCourseFirstLessonAndRedirect(course, history);\n } else {\n checkAccess();\n }\n }\n\n //eslint-disable-next-line\n checkIsTestCompleted();\n }, [id]);\n\n const isEventType = false; //course.courseType === 1;\n\n const isPending =\n subscription &&\n subscription.userSubs &&\n courseSubsStatuses(subscription.userSubs.userSubscriptionStatus, 'pending');\n const isDeclined =\n subscription &&\n (!subscription.userSubs ||\n (subscription.userSubs &&\n courseSubsStatuses(subscription.userSubs.userSubscriptionStatus, 'declined')));\n const isAccepted =\n subscription &&\n subscription.userSubs &&\n courseSubsStatuses(subscription.userSubs.userSubscriptionStatus, 'active');\n\n if (modalData === null && isDeclined && !isEventType && action === 'enroll' && isTestCompleted) {\n setModalData(subscription);\n }\n\n if (!subscription || isCheckingCourseAccess) return ;\n\n return (\n \n
\n
\n
\n
\n
\n \n {course.name}*\n \n {course.description && (\n
\n About course \n {course.description} \n
\n )}\n {course.authors && course.authors.length > 0 && (\n
\n )}\n
Lessons
\n {course.lessons && course.lessons.length > 0 &&
}\n
\n
\n
\n
\n {!!course?.image &&
}\n
\n {course.name}* \n \n
\n Registration Code \n {subsTypes[subscription.keyRequired ? 3 : subscription.type] || '-'} \n
\n
\n Parts \n {course.lessons ? course.lessons.length : '0'} parts \n
\n
\n Faculty \n {course.authors.length} Faculty \n
\n
\n Validity Period \n {subscription.expirable ? `${subscription.days} days` : 'No Expiration'} \n
\n {isDeclined && !isEventType && (\n
\n setModalData(subscription)}\n >\n Enroll\n \n
\n )}\n {isAccepted && (\n <>\n
\n
\n \n Start to: \n {subscription.userSubs.startDate.substr(0, 10)}\n \n {!!subscription.expirable && (\n \n End to: \n {subscription.userSubs.endDate.substr(0, 10)}\n \n )}\n
\n\n
\n
\n Progress {subscription.userSubs.progress || 0}%\n \n
\n
\n
\n {!isEventType && (\n
\n \n {fetch ? : 'Deactivate'}\n \n
\n )}\n >\n )}\n {isPending &&
Request Sent
}\n
\n * To get access to this course you first need to enroll\n
\n
\n
\n
\n
\n {modalData && (\n
setModalData(false)}\n onSuccess={fetchData}\n isFellowCourse={course?.name?.startsWith('Breast Fellow')}\n radiologist8HourCourseId={radiologist8HourCourseId}\n />\n )}\n \n );\n};\n\nconst mapStateToProps = state => ({\n courses: state.courses,\n});\n\nconst mapDispatchToProps = {\n getCourses,\n};\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(CourseDetails);\n","import React from 'react';\nimport BackButton from 'shared/BackButton';\n\nconst CertificateHeader = ({ history }) => {\n return (\n \n
\n \n Certificates \n
\n
\n );\n};\n\nexport default CertificateHeader;\n","import React, { useEffect } from 'react';\nimport CertificateHeader from './components/CertificateHeader';\nimport certificate from 'assets/certificate.png';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { getAndUpdateProfile } from '../../../Auth/actions/index';\n\nconst UserCertificate = ({ history }) => {\n const dispatch = useDispatch();\n const account = useSelector(state => state.account);\n const data = account.userCertificates || [];\n\n useEffect(() => {\n dispatch(getAndUpdateProfile());\n //eslint-disable-next-line\n }, []);\n\n return (\n \n
\n {!data.length && (\n
\n Certificate will be available after completion of the course.\n \n )}\n {!!data.length && (\n
\n {data.map(item => (\n
\n
\n
\n
\n ))}\n
\n )}\n
\n );\n};\n\nexport default UserCertificate;\n","import React, { useEffect } from 'react';\nimport { connect } from 'react-redux';\nimport { triggerDiagnosticsTest } from 'app/Main/components/ConnectionAndDeviceTestModal/actions';\nimport Loading from 'shared/components/Loading';\nimport { removeFromStore } from 'utils/storeHelpers';\n\nconst ConnectionTest = ({ history, triggerDiagnosticsTest }) => {\n useEffect(() => {\n removeFromStore('connectionAndDeviceTested');\n triggerDiagnosticsTest();\n history.goBack();\n }, [history, triggerDiagnosticsTest]);\n return ;\n};\n\nexport default connect(\n null,\n { triggerDiagnosticsTest },\n)(ConnectionTest);\n","/* eslint-disable react/jsx-no-target-blank */\nimport { HOST } from 'configs';\nimport React, { useState } from 'react';\nimport { IconCourses } from 'shared/components/Icons';\n\nconst data = [\n {\n header: 'Account and Certificates:',\n },\n {\n title: 'Where do I find my certificate?',\n content: (\n <>\n Certificates are emailed out automatically after completion of a course for you to download.\n They can also be access two ways:\n \n \n 1. My Transcript > Click on this link on the left-hand banner. Your\n certificate(s) will be under Certificates .\n \n \n 2. Click on your picture above your name in the left-hand banner. Scroll down to{' '}\n Certificates . Click See .\n \n \n >\n ),\n },\n {\n title: 'How do I print my certificate?',\n content: (\n <>\n Once you have located your certificates in either “My Transcript” or in your profile,\n hover over the certificate icon, the word “Download” will appear. Click on “Download”\n , a new window in your web browser will open with the certificate. From there you can select\n to download and/or save to your computer or to print.\n >\n ),\n },\n {\n title: 'How do I change my password?',\n content: (\n <>\n Click on your picture above your name. Scroll down to Change Password . Click{' '}\n Change .\n >\n ),\n },\n {\n title: 'How do I change my user profile i.e.spelling of name, etc?',\n content: (\n <>\n Click on your picture above your name. Scroll down to your information. Click Edit .\n Update your information. Click Save .\n >\n ),\n },\n {\n header: 'Courses and Modules:',\n },\n {\n title: 'Why am I unable to take certain courses?',\n content: (\n <>\n Courses that have an icon of a “lock” a registration code is required. For U.S.\n Hologic customer needing to register for Essentials of 3D Breast Tomosynthesis Course \n , please reach out to your Account Executive for the code.\n >\n ),\n },\n {\n title: 'Why am I unable to take certain modules within a course?',\n content: (\n <>\n In order to proceed to the next module, some courses require that you watch each module in\n its entirety. To proceed to the next module, ensure that you have watched your current\n module in its entirety. Also, for courses containing hands-on case review, every view in\n each case must be viewed and scored. Please ensure there is a “checkmark” over each\n view for each cases to be counted as complete.\n >\n ),\n },\n {\n title: 'Is there a cheat sheet for the DICOM viewer tools?',\n content: (\n <>\n Yes, that can be downloaded from{' '}\n \n here\n \n .\n >\n ),\n },\n {\n title:\n 'While in the hands-on case review module, tomosynthesis slices or cine are slow and choppy',\n content: (\n <>\n The tomosynthesis slices have to be fully downloaded before they run smoothly. To initiate\n the buffering if they are slow or choppy, click on the “play” icon on the stop of the\n scroll bar when you are on a tomosynthesis image. Press the “play” icon and the cases\n will buffer for a second to download the slices. Once the buffering has stopped, the\n tomosynthesis image will scroll smoothly.\n >\n ),\n },\n {\n title: 'Why can I view the videos but cannot access the hands-on case review section?',\n content: (\n <>\n This has to do with the internet network you are using, either a firewall or bandwidth. If\n you are in a hospital setting, the firewalls may be prohibiting the case review. If it is\n not a firewall issue then it may be a bandwidth issue. The hands-on case review section\n needs a lot more bandwidth than the videos to play.\n >\n ),\n },\n {\n header: 'Specifications and Connection:',\n },\n {\n title: 'What are the ideal specifications for internet browsers?',\n content: (\n \n \n ● Google Chrome (min version 79.0.3945 )\n \n \n ● Mozilla Firefox (min version 78.0 )\n \n \n ● Safari (Mac) (min version 13 )\n \n \n ● Microsoft Edge (min version 79.0.309 )\n \n ● Internet Explorer is no longer supported \n \n ),\n },\n {\n title: 'What are the ideal specifications for computer?',\n content: (\n \n ● Memory: 2 GB or more Memory: 2 GB or more \n ● Storage: 8 GB or more free space Storage: 8 GB or more free space \n \n ● Monitor/Display: at least 13\" monitor with resolution 1600 x 900 or better\n Monitor/Display: at least 13\" monitor with resolution 1600 x 900 or better\n \n ● Internet: 8 Mb/s or better Internet: 8 Mb/s or better \n \n ),\n },\n {\n title: 'I failed the Connection Test, can I still access the portal and what does it mean',\n content:\n 'If you fail the Connect Test you can still access the portal, just means that some aspects of the portal may not run optimally. Depending on what section failed, you may need to try a different internet network or web browser.',\n },\n];\n\nconst FAQ = () => {\n const [activeIndex, setActiveIndex] = useState(null);\n\n const toggleAccordion = index => setActiveIndex(activeIndex === index ? null : index);\n return (\n \n
\n
\n
\n {data.map((item, index) => {\n if (item.header)\n return (\n
\n
{item.header} \n \n );\n return (\n
\n toggleAccordion(index)}\n role='presentation'\n >\n {item.title}
\n \n {activeIndex === index && {item.content}
}\n \n );\n })}\n
\n
\n
\n );\n};\n\nexport default FAQ;\n","/* eslint-disable no-console */\nimport React, { useRef, useEffect, useState } from 'react';\nimport * as cornerstone from 'cornerstone-core';\nimport { HOST } from 'configs';\n\nconst original = `${\n HOST.API.CONTENT\n}/dicom/2018_CHDI2D_107_BTOJPEGLossless_MG_L_MLO_TomoHD_24X29_20180331_/img_1_lut.jpg`;\n\nconst TestViewerWL = () => {\n const [imageUrl, setImageUrl] = useState(\n `${\n HOST.API.CONTENT\n }/dicom/2018_CHDI2D_107_BTOJPEGLossless_MG_L_MLO_TomoHD_24X29_20180331_/img_1.jpg`,\n );\n const [voi, setVoi] = useState({\n windowCenter: 0,\n windowWidth: 0,\n });\n const el1 = useRef(null);\n const el2 = useRef(null);\n\n const onImageRendered = () => {\n try {\n const viewport = cornerstone.getViewport(el1.current);\n setVoi(viewport.voi);\n } catch (err) {\n console.log(err);\n }\n };\n\n const onWindowResize = () => {\n cornerstone.resize(el1.current);\n };\n\n const loadImage = async (element, url) => {\n const image = await cornerstone.loadAndCacheImage(url || imageUrl);\n const viewportOptions = { pixelReplication: false, image };\n await cornerstone.displayImage(element, image, viewportOptions);\n };\n\n const initElement = async element => {\n await cornerstone.enable(element);\n await cornerstone.resize(element);\n element.addEventListener('cornerstoneimagerendered', onImageRendered);\n window.addEventListener('resize', onWindowResize);\n };\n\n const initData = async (element, url) => {\n await loadImage(element, url);\n await cornerstone.fitToWindow(element);\n await cornerstone.resize(element);\n };\n\n const unmountElement = async () => {\n // element.current.removeEventListener('cornerstoneimagerendered', onImageRendered);\n // window.removeEventListener('resize', onWindowResize);\n // cornerstone.disable(el1.current);\n };\n\n const onImageChange = e => {\n e.preventDefault();\n initData(el1.current);\n };\n\n const onChangeVoi = ({ target }) => {\n const body = { ...voi, [target.name]: target.value };\n setVoi(body);\n const viewport = cornerstone.getViewport(el1.current);\n viewport.voi = body;\n cornerstone.setViewport(el1.current, viewport);\n };\n\n const onChangeVoiSubmit = e => {\n e.preventDefault();\n const viewport = cornerstone.getViewport(el1.current);\n viewport.voi = voi;\n cornerstone.setViewport(el1.current, viewport);\n };\n\n useEffect(() => {\n initElement(el1.current);\n initElement(el2.current);\n return () => unmountElement();\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n initData(el1.current);\n initData(el2.current, original);\n //eslint-disable-next-line\n }, []);\n\n return (\n \n
\n
\n \n Image \n setImageUrl(target.value)}\n value={imageUrl}\n />\n
\n {/* \n change image\n */}\n \n
\n \n \n change level\n \n \n
\n
\n
\n );\n};\n\nexport default TestViewerWL;\n","import React from 'react';\nimport { IconCourses } from 'shared/components/Icons';\n\nconst Requirements = () => {\n return (\n \n
\n
\n \n \n \n System Requirements\n
\n
\n
\n
Browser \n
\n ● Chrome (min version 79.0.3945 )\n
\n
\n ● Mozilla Firefox (min version 78.0 )\n
\n
\n ● Safari Mac (min version 13 )\n
\n
\n ● Microsoft Edge (min version 79.0.309 )\n
\n
\n
Hardware \n
● Standard laptop, desktop computer or iPad
\n
● Resolution of at least 1366×768
\n
\n
Internet Connection \n
● No firewall blocking ACE domain (hologicace.com)
\n
● Bandwidth 7 MBits/s connection
\n
\n
Software \n
● Google Chrome
\n
● Mozilla Firefox
\n
● Safari (Mac)
\n
● Microsoft Edge
\n
\n
\n );\n};\n\nexport default Requirements;\n","import React from 'react';\nimport moment from 'moment';\n\nconst EventLine = ({ label, value, className }) => (\n \n {label} \n {value} \n
\n);\n\nconst getMapUrl = item => {\n return `https://maps.google.com/maps?q=${item.lat},${item.lng}&hl=es&z=14&&output=embed`;\n};\n\nconst entranceType = {\n 0: 'Open',\n 1: 'Invitation',\n 2: 'Registration',\n};\n\nconst eventTypes = {\n 0: 'Webinar',\n 1: 'TradeShow',\n};\n\nconst typeNames = {\n 0: 'Online',\n 1: 'Offline',\n};\n\nconst EventInfo = ({ event }) => {\n const isWebinar = eventTypes[event.eventType] === 'Webinar';\n\n return (\n \n
\n
\n \n \n \n \n \n \n \n \n \n
\n {!isWebinar && (\n
\n \n
\n )}\n
\n
\n );\n};\n\nexport default EventInfo;\n","/* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */\nimport React, { useEffect } from 'react';\nimport { useSnackbar } from 'notistack';\nimport { IconAbout } from 'shared/components/Icons';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { getError, getEventId } from 'utils/appHelpers';\nimport { onGetEvents } from 'app/Events/actions';\nimport { Api } from 'utils/connectors';\nimport Loading from 'shared/components/Loading';\nimport EventInfo from 'app/Events/routes/EventLanding/components/EventInfo';\n\nconst AboutEvent = () => {\n const { enqueueSnackbar } = useSnackbar();\n const dispatch = useDispatch();\n const events = useSelector(state => state.events);\n const id = getEventId();\n const event = events[id];\n\n const getEventData = async id => {\n try {\n const { data } = await Api.get(`/events/event/${id}`);\n dispatch(onGetEvents({ ...events, [id]: data.data }));\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n useEffect(() => {\n getEventData(id);\n //eslint-disable-next-line\n }, [])\n\n if (!event) return ;\n\n return (\n \n
\n
\n \n \n \n About\n
\n
\n
\n
\n
\n
\n *Not all products or technologies featured are available in all markets\n
\n
\n
\n );\n};\n\nexport default AboutEvent;\n","export default __webpack_public_path__ + \"static/media/kol_icon.47fa6254.svg\";","import { useRef, useEffect } from 'react';\n\n/**\n * A hook that keeps track of the previous value of a state or Redux state.\n *\n * @param value - The current value of the state.\n * @returns The previous value of the state.\n */\nexport const usePrevious = value => {\n const ref = useRef();\n\n useEffect(() => {\n ref.current = value;\n }, [value]);\n\n return ref.current;\n};\n","import React from 'react';\nimport { connect, useSelector } from 'react-redux';\nimport AuthorsList from './../CourseDetails/components/AuthorsList';\nimport LessonsList from './../CourseDetails/components/LessonsList';\nimport { IconKey, IconLock } from 'shared/components/Icons';\nimport TagsViewBlock from 'shared/components/TagsViewBlock';\nimport { useState } from 'react';\nimport { Api } from 'utils/connectors';\nimport {\n findCourseFirstLesson,\n findCourseFirstLessonAndRedirect,\n getError,\n} from 'utils/appHelpers';\nimport { useSnackbar } from 'notistack';\nimport { formatSubscription } from 'utils/formatHelpers';\nimport { useEffect } from 'react';\nimport Loading from 'shared/components/Loading';\nimport { formatCourseData, getCourses } from '../Courses/actions';\nimport { API_RESPONSE_STATUS_CODES } from 'configs/constants';\n\nconst subsTypes = {\n 1: 'No Key Required',\n 2: 'Paid',\n 3: (\n <>\n Key Required\n >\n ),\n};\n\nconst CourseDetailsPublic = ({ history, match, getCourses }) => {\n const { enqueueSnackbar } = useSnackbar();\n const { id } = match.params;\n const isCourseIdShareToken = isNaN(id);\n const [subscription, setSubscription] = useState(null);\n const courses = useSelector(store => store.courses);\n const userCourse = courses.find(item => item.id === Number(id));\n const [course, setCourse] = useState(null);\n const [isCheckingCourseAccess, setIsCheckingCourseAccess] = useState(false);\n\n const fetchData = async () => {\n try {\n const { data } = await Api.get(`courses/all-courses?id=${id}`);\n const course = data.data ? formatCourseData(data.data)[0] : null;\n const subs = course && course.subscriptionPlans[0];\n if (course) {\n setCourse(course);\n setSubscription(formatSubscription(subs));\n } else {\n history.push('/');\n }\n } catch (err) {\n history.push('/');\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const checkAccess = async () => {\n try {\n setIsCheckingCourseAccess(true);\n const res = await Api.get(`/courses/check-to-enroll/${id}`);\n if (\n res?.status === API_RESPONSE_STATUS_CODES.Success ||\n res?.status === API_RESPONSE_STATUS_CODES.CreateUpdate\n ) {\n const needToGetCourses = res?.status === API_RESPONSE_STATUS_CODES.CreateUpdate;\n findCourseFirstLessonAndRedirect(course, history, needToGetCourses);\n }\n } catch (err) {\n return;\n } finally {\n setIsCheckingCourseAccess(false);\n }\n };\n\n const onClickSignIn = () => {\n history.push({ pathname: '/login', state: { loginForwardUrl: `/courses/${id}` } });\n };\n\n useEffect(() => {\n if (id) {\n fetchData();\n }\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n if (userCourse) {\n if (userCourse.userCourse) {\n findCourseFirstLessonAndRedirect(userCourse, history);\n } else {\n if (!isCourseIdShareToken) {\n checkAccess();\n }\n }\n }\n }, [course]);\n\n if (!subscription || !course || isCheckingCourseAccess) {\n return ;\n }\n\n return (\n \n
\n
\n
\n
\n
\n \n {course.name}*\n \n {course.description && (\n
\n About course \n {course.description} \n
\n )}\n {course.authors && course.authors.length > 0 && (\n
\n )}\n
Lessons
\n {course.lessons && course.lessons.length > 0 &&
}\n
\n
\n
\n
\n
\n
\n {course.name}* \n \n
\n Registration Code \n {subsTypes[subscription.type] || '-'} \n
\n
\n Parts \n {course.lessons ? course.lessons.length : '0'} parts \n
\n
\n Faculty \n {course.authors.length} Faculty \n
\n
\n Validity Period \n \n {subscription.expirable ? `${subscription.days} days` : 'No Expiration'}\n \n
\n
\n \n Sign In\n \n
\n
\n * To get access to this course you first need to Sign In\n
\n
\n
\n
\n
\n
\n
\n );\n};\n\nconst mapStateToProps = state => ({\n courses: state.courses,\n});\n\nconst mapDispatchToProps = {\n getCourses,\n};\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(CourseDetailsPublic);\n","import React, { useState, useEffect } from 'react';\nimport { Route, Switch, Redirect } from 'react-router-dom';\nimport { connect, useSelector } from 'react-redux';\nimport { getAuthors } from './routes/Authors/actions';\nimport { getCourses, getMarketingCourses } from './routes/Courses/actions';\n\nimport SideBar from './components/SideBar/index';\nimport HeaderNav from './components/HeaderNav/index';\nimport Home from './routes/Home';\nimport Profile from './routes/Profile';\nimport DiagnosticsHistory from './routes/DiagnosticsHistory';\nimport Courses from './routes/Courses';\nimport Transcript from './routes/Transcript';\nimport Subscriptions from './routes/Subscriptions';\nimport SubscriptionView from './routes/SubscriptionView';\nimport CourseView from './routes/CourseView';\nimport Authors from './routes/Authors';\n// import AuthorView from './routes/AuthorView';\n// import Statistics from './routes/Statistics';\nimport About from './routes/About';\nimport Contacts from './routes/Contacts';\nimport Messages from './routes/Messages';\nimport PrivacyAndPolicy from './routes/PrivacyAndPolicy';\nimport Viewer from './routes/Viewer';\nimport TestViewer from './routes/TestViewer';\nimport Icons from './routes/Icons';\nimport Loading from 'shared/components/Loading';\nimport ConnectionAndDeviceTestModal from './components/ConnectionAndDeviceTestModal';\nimport CourseProgress from './routes/CourseProgress';\nimport CourseCertificate from './routes/CourseCertificate';\n// import CourseFeedback from './routes/CourseFeedback';\n// import CourseSubscriptions from './routes/CourseSubscriptions';\nimport CourseDetails from './routes/CourseDetails';\nimport { getAndUpdateProfile } from 'app/Auth/actions';\nimport UserCertificate from './routes/UserCertificate';\n// import FooterNav from 'shared/components/FooterNav';\nimport ConnectionTest from './routes/ConnectionTest';\nimport FAQ from './routes/FAQ';\nimport TestViewerWL from './routes/TestWL';\nimport Requirements from './routes/Requirements';\nimport { hasAccess, PRoute, WithTitleRoute } from 'utils/permissionHelper';\nimport AboutEvent from './routes/AboutEvent';\nimport { getLocalToken } from 'utils/tokenHelpers';\nimport { HOST } from 'configs';\nimport kolIcon from 'assets/kol_icon.svg';\nimport { getFromStore, removeFromStore } from 'utils/storeHelpers';\nimport { usePrevious } from 'shared/hooks/usePrevious';\nimport CourseDetailsPublic from './routes/CourseDetailsPublic';\n\nconst Main = ({\n getAuthors,\n getCourses,\n getMarketingCourses,\n getAndUpdateProfile,\n match,\n history,\n location,\n}) => {\n const [testing, setTesting] = useState(false);\n const authors = useSelector(item => item.authors);\n const courses = useSelector(item => item.courses);\n const marketingCourses = useSelector(item => item.marketingCourses);\n const connectionAndDeviceTest = useSelector(item => item.connectionAndDeviceTest);\n const user = useSelector(item => item.account);\n const previousUserId = usePrevious(user?.id);\n const path = match.path === '/' ? '' : match.path;\n const isViewer = window.location.pathname.includes('/play');\n const isHomePage = window.location.pathname.includes('/home');\n const redirectUrl = getFromStore('redirectUrl');\n const isVisitorUser = hasAccess('visitor_user');\n\n const finishTest = () => {\n setTesting(false);\n getAndUpdateProfile();\n };\n\n const openAdmin = () => {\n const token = getLocalToken();\n window.open(`${HOST.API.ADMIN_DOMAIN}/grant/kol?t=${token}`, '_blank');\n };\n\n useEffect(() => {\n if (!courses || user?.id !== previousUserId) getCourses();\n }, [courses, getCourses, isVisitorUser, user]);\n\n useEffect(() => {\n if (!marketingCourses) getMarketingCourses();\n }, [marketingCourses, getMarketingCourses]);\n\n useEffect(() => {\n if (!authors) getAuthors();\n if ((authors && courses, redirectUrl)) {\n history.push(redirectUrl);\n removeFromStore('redirectUrl');\n }\n //eslint-disable-next-line\n }, [authors, getAuthors, courses]);\n\n useEffect(() => {\n if (!getFromStore('connectionAndDeviceTested') && !isVisitorUser) setTesting(true);\n }, [connectionAndDeviceTest]);\n\n if (!authors || !courses) return ;\n\n const redirect = hasAccess('vip_user') ? '/home' : '/allcourses';\n const isDirectAccessToRoute = !location.key;\n const isCourseDetailsPage = location.pathname.startsWith('/allcourses/details');\n\n return (\n \n
\n {!isHomePage && !(isVisitorUser && isDirectAccessToRoute && isCourseDetailsPage) && (\n \n )}\n
\n {isHomePage && }\n {/* */}\n {hasAccess('kol_user') && !isViewer && !isHomePage && (\n \n
\n \n \n
\n )}\n \n \n \n \n \n \n {/* */}\n {!isVisitorUser && (\n \n )}\n {isVisitorUser && (\n \n )}\n \n \n {/* */}\n \n \n \n \n \n \n \n \n {/* */}\n \n {/* */}\n \n \n \n \n \n \n \n \n \n \n \n \n \n {testing &&
}\n
\n
\n );\n};\n\nexport default connect(\n null,\n { getAuthors, getCourses, getMarketingCourses, getAndUpdateProfile },\n)(Main);\n","import React, { useRef, useEffect, useState } from 'react';\nimport { useSnackbar } from 'notistack';\nimport * as cornerstone from 'cornerstone-core';\nimport Select from 'shared/components/Select';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport cornerstoneTools from 'cornerstone-tools/dist/cornerstoneTools';\nimport Loading from 'shared/components/Loading';\n\nconst cases = [\n { id: 156, name: '3_AITomoSlice_101' },\n { id: 157, name: '3_AITomoSlice_103' },\n { id: 158, name: '3_AITomoSlice_104' },\n { id: 159, name: '3_AITomoSlice_105' },\n { id: 160, name: '3_AITomoSlice_108' },\n { id: 161, name: '3_AITomoSlice_109' },\n { id: 162, name: '3_AITomoSlice_111' },\n { id: 163, name: '3_AITomoSlice_112' },\n { id: 164, name: '3_AITomoSlice_113' },\n { id: 165, name: '3_AITomoSlice_114' },\n { id: 166, name: '3_AITomoSlice_116' },\n { id: 167, name: '3_AITomoSlice_118' },\n { id: 168, name: '3_AITomoSlice_119' },\n { id: 169, name: '3_AITomoSlice_120' },\n];\n\nconst PublicViewer = () => {\n const { enqueueSnackbar } = useSnackbar();\n const [activeCase, setActiveCase] = useState(156);\n const [activeImage, setActiveImage] = useState();\n const [dicomItems, setDicomItems] = useState();\n const [fetch, setFetch] = useState(false);\n const el1 = useRef(null);\n\n const getCaseImages = async () => {\n try {\n setFetch(true);\n const { data } = await Api.get(`/cases/getcasebyid/${activeCase}`);\n const items = data.data.caseDicomItems.map(({ dicomItem }) => {\n return {\n id: dicomItem.dicomItemImages ? dicomItem.dicomItemImages[0].url : '',\n name: dicomItem.fileName,\n };\n });\n setDicomItems(items);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setFetch(false);\n }\n };\n\n const onWindowResize = () => {\n cornerstone.resize(el1.current);\n };\n\n const loadImage = async url => {\n const image = await cornerstone.loadAndCacheImage(url);\n const viewportOptions = { pixelReplication: false, image };\n await cornerstone.displayImage(el1.current, image, viewportOptions);\n };\n\n const initElement = async () => {\n await cornerstone.enable(el1.current);\n await cornerstone.resize(el1.current);\n window.addEventListener('resize', onWindowResize);\n const options = {\n configuration: {\n invert: false,\n preventZoomOutsideImage: false,\n minScale: 0.01,\n maxScale: 5.0,\n },\n };\n cornerstoneTools.addToolForElement(el1.current, cornerstoneTools.ZoomMouseWheelTool, options);\n cornerstoneTools.addTool(cornerstoneTools.PanTool);\n cornerstoneTools.setToolActive('Pan', { mouseButtonMask: 1 });\n cornerstoneTools.setToolActive('ZoomMouseWheel', { mouseButtonMask: 1 });\n };\n\n const initData = async url => {\n await loadImage(url);\n await cornerstone.fitToWindow(el1.current);\n await cornerstone.resize(el1.current);\n };\n\n useEffect(() => {\n initElement();\n //eslint-disable-next-line\n }, []);\n\n useEffect(() => {\n getCaseImages(activeCase);\n //eslint-disable-next-line\n }, [activeCase]);\n\n useEffect(() => {\n if (activeImage) initData(activeImage);\n //eslint-disable-next-line\n }, [activeImage]);\n\n return (\n \n
\n {fetch && }\n setActiveCase(target.value)}\n />\n {dicomItems && (\n setActiveImage(target.value)}\n />\n )}\n
\n
\n
\n );\n};\n\nexport default PublicViewer;\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\n/* eslint-disable jsx-a11y/anchor-is-valid */\nimport React, { useRef, useState } from 'react';\nimport logo from 'assets/white-logo.svg';\n// import accessImg from 'assets/events/accesslogo.svg';\nimport { NavLink } from 'react-router-dom';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { IconArrowDown } from 'shared/components/Icons';\nimport { logout } from 'app/Auth/actions';\nimport useOutsideClick from 'shared/hooks/useOutsideClick';\nimport { hasAccess } from 'utils/permissionHelper';\n\nconst EventHeader = ({ eventId, name }) => {\n const dispatch = useDispatch();\n // const account = useSelector(state => state.account);\n // const [dropMenu, setDropMenu] = useState();\n const [accountMenu, setAccountMenu] = useState();\n const isAuthUser = useSelector(state => state.isAuthenticated);\n const isVisitorUser = hasAccess('visitor_user');\n const isAuth = isAuthUser && !isVisitorUser;\n // const dropMenuEl = useRef();\n const accountMenuEl = useRef();\n\n const homeUrl = `/events/${name}`;\n\n const logoutProfile = e => {\n e.preventDefault();\n dispatch(logout());\n };\n\n // const onClickAccessVip = () => {\n // const isAlreadyVIP = account.userType !== 6;\n // let win;\n // if (isAlreadyVIP) {\n // win = window.open('/allcourses', '_blank');\n // } else {\n // win = window.open('/register', '_blank');\n // }\n // win.focus();\n // };\n\n // useOutsideClick(dropMenuEl, () => setDropMenu(false));\n useOutsideClick(accountMenuEl, () => setAccountMenu(false));\n\n return (\n \n {/*
*/}\n {/*
*/}\n {/* {!!isAuth && (\n
\n \n \n )} */}\n {/*
*/}\n
\n
\n
\n
\n \n \n Home\n \n \n {!!isAuth && (\n \n Available Courses \n \n )}\n {/* \n setDropMenu(!dropMenu)}\n ref={dropMenuEl}\n >\n Additions
\n {dropMenu && (\n
\n )}\n
\n */}\n {!isAuth && (\n \n \n Register\n \n \n )}\n {!isAuth && (\n \n \n Login\n \n \n )}\n {!!isAuth && (\n \n setAccountMenu(!accountMenu)}\n ref={accountMenuEl}\n >\n Account
\n {accountMenu && (\n
\n )}\n
\n \n )}\n \n
\n
\n
\n );\n};\n\nexport default EventHeader;\n","export default __webpack_public_path__ + \"static/media/location.1204077f.svg\";","export default __webpack_public_path__ + \"static/media/calendar.9d44fad7.svg\";","export default __webpack_public_path__ + \"static/media/speakers.51a396cf.svg\";","/* eslint-disable jsx-a11y/no-static-element-interactions */\n/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */\nimport React from 'react';\nimport moment from 'moment';\nimport { scrollToEl } from 'utils/appHelpers';\n// import Icon1 from 'assets/home-icon-1.svg';\n// import Icon2 from 'assets/home-icon-2.svg';\n// import { IconArrowRight } from 'shared/components/Icons';\n// import { useHistory } from 'react-router';\nimport FooterIcon1 from 'assets/events/location.svg';\nimport FooterIcon2 from 'assets/events/calendar.svg';\nimport FooterIcon3 from 'assets/events/speakers.svg';\n// import FooterIcon4 from 'assets/events/seats.svg';\nimport Slider from 'react-slick';\n// import { useSelector } from 'react-redux';\n\nconst sliderSettings = {\n arrows: false,\n dots: false,\n speed: 500,\n infinite: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n autoplaySpeed: 7000,\n className: 'event-slide',\n autoplay: true,\n};\n\nconst EventBanner = ({ event }) => {\n // const isAuth = useSelector(state => state.isAuthenticated);\n // const account = useSelector(state => state.account);\n // const history = useHistory();\n // const openUrl = url => {\n // window.open(url, '_blank');\n // };\n\n // const onClickStart = () => {\n // if (isAuth) {\n // history.push(`/events/${event.id}/dashboard/allcourses`);\n // } else {\n // history.push(`/events/${event.id}/register`);\n // }\n // };\n\n // const onClickAccessVip = () => {\n // const isAlreadyVIP = account.userType !== 6;\n // let win;\n // if (isAlreadyVIP) {\n // win = window.open('/allcourses', '_blank');\n // } else {\n // win = window.open('/register', '_blank');\n // }\n // win.focus();\n // };\n\n // const isExpired = moment(event.endDate).diff(new Date()) <= 0;\n\n return (\n \n
\n
\n
\n
\n
\n scrollToEl('aboutSection')}>About \n scrollToEl('noncmeSection')}>Non-CME \n scrollToEl('cmeSection')}>CME \n scrollToEl('speakersSection')}>Faculty \n {/* scrollToEl('programSection')}>Program */}\n \n
\n
\n {/*
\n
\n
\n
WEBINARS \n
\n
\n
\n
\n
\n
\n
LIVE EVENTS \n
\n
\n
*/}\n
\n {/* \n Start\n */}\n {/* {!!isAuth && (\n \n Access ACE\n \n )} */}\n
\n
\n
{event?.name}
\n
\n
\n {event.banners.map(item => {\n return (\n \n
\n
\n );\n })}\n \n
\n
\n
\n \n \n \n
Worldwide
\n
Online Event
\n
\n \n \n \n \n
{moment(event.startDate).format('MM/DD/YYYY')}
\n
{moment(event.endDate).format('MM/DD/YYYY')}
\n
\n \n \n \n \n
{event.programs.length} Programs
\n
{event.speakers.length} Faculty
\n
\n \n {/* \n \n \n
\n {event.capacity < 1 ? 'No Limit' : `${event.capacity} Seats`}\n
\n
Hurry up, register!
\n
\n */}\n \n
\n
\n
\n );\n};\n\nexport default EventBanner;\n","import React from 'react';\n\nconst EventAbout = ({ event }) => {\n return (\n \n
\n
About Event \n
\n
\n *Not all products or technologies featured are available in all markets\n
\n
\n
\n );\n};\n\nexport default EventAbout;\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React, { useState } from 'react';\nimport { IconArrowDown, IconArrowUp } from 'shared/components/Icons';\n\nconst EventSpeakers = ({ event }) => {\n const [open, setOpen] = useState(false);\n\n return (\n \n );\n};\n\nexport default EventSpeakers;\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\n/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */\nimport React from 'react';\nimport { IconCirclePlay, IconLock } from 'shared/components/Icons';\n\nconst EventCME = ({ event }) => {\n const openItemUrl = url => {\n window.open(url, '_blank');\n };\n\n return (\n \n
\n
\n CME \n Courses\n \n
\n {event.cmEs &&\n event.cmEs.map(item => {\n const Icon = false ? IconLock : IconCirclePlay;\n return (\n
\n
\n
\n \n
\n
\n
\n
\n
\n {item.title}\n
\n
{item.description}
\n
\n Open\n \n
\n
\n );\n })}\n
\n
\n World Class CME is accredited by the Accreditation Council for Continuing Medical\n Education for Physicians and designates this activity for a maximum of 1 AMA PRA Category\n 1 Credit™. Attendees should claim only the credit commensurate with the extent of their\n participation in the activity. This activity is supported by an educational grant from\n Hologic.\n
\n
\n
\n );\n};\n\nexport default EventCME;\n","/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */\n/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React from 'react';\nimport { useSelector } from 'react-redux';\nimport { IconCirclePlay, IconLock } from 'shared/components/Icons';\nimport { saveToStore } from 'utils/storeHelpers';\n\nconst EventCourses = ({ event }) => {\n const isAuth = useSelector(state => state.isAuthenticated);\n\n const openItemUrl = () => {\n if (!isAuth) saveToStore('redirect', `/events/${event.id}/dashboard/allcourses`);\n const url = isAuth\n ? `/events/${event.eventUrl}/dashboard/allcourses`\n : `/events/${event.eventUrl}/login`;\n window.open(url, '_blank');\n };\n\n return (\n \n
\n
\n Non-CME \n Courses\n \n
\n {event.courses &&\n event.courses.map(item => {\n const Icon = item.isLocked ? IconLock : IconCirclePlay;\n return (\n
\n
\n
\n \n
\n
\n
\n
\n
\n {item.title}\n
\n
{item.description}
\n
\n Open\n \n
\n
\n );\n })}\n
\n
\n
\n );\n};\n\nexport default EventCourses;\n","export default __webpack_public_path__ + \"static/media/fb.4420d86f.svg\";","export default __webpack_public_path__ + \"static/media/twitter.b40321b7.svg\";","export default __webpack_public_path__ + \"static/media/linkedin.9016e7e5.svg\";","export default __webpack_public_path__ + \"static/media/footer-image.dd6e9f93.png\";","import React from 'react';\nimport img1 from 'assets/events/fb.svg';\nimport img2 from 'assets/events/twitter.svg';\nimport img3 from 'assets/events/linkedin.svg';\nimport footerImg from 'assets/events/footer-image.png';\n\nconst soc = [\n {\n img: img1,\n url: 'https://www.facebook.com/Hologic',\n },\n {\n img: img2,\n url: 'https://twitter.com/Hologic',\n },\n {\n img: img3,\n url: 'https://www.linkedin.com/company/hologic',\n },\n];\n\nconst EventFooter = ({ event }) => {\n return (\n \n );\n};\n\nexport default EventFooter;\n","import React, { useEffect } from 'react';\nimport { useSnackbar } from 'notistack';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport Loading from 'shared/components/Loading';\nimport EventHeader from './components/EventHeader';\nimport EventBanner from './components/EventBanner';\n// import EventInfo from './components/EventInfo';\nimport EventAbout from './components/EventAbout';\nimport EventSpeakers from './components/EventSpeakers';\n// import EventPrograms from './components/EventPrograms';\nimport EventCME from './components/EventCME';\nimport EventCourses from './components/EventCourses';\nimport EventFooter from './components/EventFooter';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { onGetEvents } from 'app/Events/actions';\n\nconst EventLanding = ({ match }) => {\n const dispatch = useDispatch();\n const events = useSelector(state => state.events);\n const { name } = match.params;\n const event = events[name];\n const { enqueueSnackbar } = useSnackbar();\n\n const getEventData = async ({ name }) => {\n try {\n const key = name;\n const endpoint = `/events/event-url/${name}`;\n\n const { data } = await Api.get(endpoint);\n const evtId = data?.data?.id;\n const res = await Api.get(`/events/event/courses/${evtId}`);\n\n data.data.courses = res.data.data.courses;\n dispatch(onGetEvents({ ...events, [key]: data.data }));\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n useEffect(() => {\n if (name) getEventData({ name });\n //eslint-disable-next-line\n }, []);\n\n if (!event) return ;\n\n return (\n \n \n \n {/* */}\n \n \n \n \n {/* */}\n \n
\n );\n};\n\nexport default EventLanding;\n","export default __webpack_public_path__ + \"static/media/ACE-logo-blue.8a26e656.svg\";","import { loginUser } from 'app/Auth/actions';\nimport React, { useState } from 'react';\nimport { useDispatch } from 'react-redux';\nimport ButtonLine from 'shared/components/ButtonLine';\nimport { IconMail, IconPassword } from 'shared/components/Icons';\nimport InputGroup from 'shared/components/InputGroup';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport { getFromStore } from 'utils/storeHelpers';\nimport EventHeader from '../EventLanding/components/EventHeader';\nimport blueLogo from 'assets/ACE-logo-blue.svg';\n\nconst EventLogin = ({ match, history }) => {\n const hasRedirect = getFromStore('redirect');\n const { id, name } = match.params;\n const dispatch = useDispatch();\n const [error, setError] = useState();\n const [fetch, setFetch] = useState();\n const [auth, setAuth] = useState({\n email: '',\n password: '',\n eventId: id,\n eventUrl: name,\n });\n\n const handleSubmit = async e => {\n e.preventDefault();\n setFetch(true);\n try {\n const res = await Api.post('/auth/login', auth);\n if (res.data.data.pendingConfirmation) {\n history.push({ pathname: 'email-verification', state: { email: auth.email } });\n return;\n }\n await dispatch(loginUser(res.data, history, `/events/${name}`));\n setFetch(false);\n } catch (err) {\n setError(getError(err));\n setFetch(false);\n }\n };\n\n const handleChange = ({ target }) => {\n const { name, value } = target;\n const tempAuth = { ...auth };\n tempAuth[name] = value;\n setAuth(tempAuth);\n };\n\n return (\n \n
\n
\n
\n
\n
\n
\n
\n }\n required\n />\n }\n required\n />\n {error && {error}
}\n \n \n Sign In\n \n
\n \n
\n
\n
\n );\n};\n\nexport default EventLogin;\n","import React from 'react';\n\nconst AuthPage = ({ children, className = '' }) => {\n return {children}
;\n};\n\nexport default AuthPage;\n","import React, { useState } from 'react';\nimport InputGroup from 'shared/components/InputGroup';\nimport { getError } from 'utils/appHelpers';\nimport { Api } from 'utils/connectors';\nimport EventHeader from '../EventLanding/components/EventHeader';\nimport Select from 'shared/components/Select';\nimport { countries, degrees, specialty, states } from 'app/Auth/routes/Register/configs';\nimport blueLogo from 'assets/ACE-logo-blue.svg';\nimport AuthPage from 'app/Auth/components/AuthPage';\nimport Button from 'shared/components/Button';\n\nconst EventRegister = ({ match, history }) => {\n const { id, name } = match.params;\n const [error, setError] = useState();\n const [fetch, setFetch] = useState();\n const [isConfirm, setIsConfirm] = useState(false);\n const [form, setForm] = useState({\n fname: '',\n lname: '',\n email: '',\n institution: '',\n countryId: '',\n city: '',\n address: '',\n phone: '',\n zipCode: '',\n degree: '',\n specialty: '',\n prefferedNameOnCertificate: '',\n password: '',\n rpassword: '',\n state: '',\n eventId: Number(id),\n eventUrl: name,\n });\n\n const areAllRequiredFieldsFilled = () => {\n const requiredFields = [\n 'fname',\n 'lname',\n 'degree',\n 'specialty',\n 'email',\n 'password',\n 'rpassword',\n 'institution',\n 'countryId',\n 'city',\n 'state',\n 'zipCode',\n 'address',\n 'phone',\n ];\n return requiredFields.every(field => Boolean(form[field]));\n };\n\n const handleSubmit = async e => {\n e.preventDefault();\n setFetch(true);\n try {\n const body = {\n email: form.email,\n password: form.password,\n firstname: form.fname,\n lastname: form.lname,\n usertype: 1,\n countryid: form.countryId,\n eventUrl: name,\n learnerprofile: {\n state: form.state,\n institution: form.institution,\n city: form.city,\n address: form.address,\n zipCode: form.zipCode,\n phone: form.phone,\n degree: form.degree,\n profession: form.specialty,\n prefferedNameOnCertificate: form.prefferedNameOnCertificate,\n },\n };\n await Api.post('/auth/register', body);\n history.push({\n pathname: `/events/${name}/email-verification`,\n state: { email: form.email },\n });\n setIsConfirm(true);\n } catch (err) {\n setError(getError(err));\n } finally {\n setFetch(false);\n }\n };\n\n const handleChange = ({ target }) => {\n const { name, value } = target;\n const tempForm = { ...form };\n tempForm[name] = value;\n setForm(tempForm);\n };\n\n const isCountryUs = form.countryId === '226';\n const passwordMatch = form.password === form.rpassword;\n\n return (\n \n
\n
\n
\n
\n
\n \n Personal Information \n \n \n \n
\n \n \n \n
\n \n \n
\n \n \n
\n \n \n \n
\n {!passwordMatch && form.rpassword && (\n Passwords mismatch
\n )}\n Institute Information \n \n \n
\n \n \n {isCountryUs ? (\n \n ) : (\n \n )}\n \n
\n \n \n
\n \n \n
\n \n {error &&
{error}
}\n
\n Next\n \n
\n \n \n
\n );\n};\n\nexport default EventRegister;\n","import React, { useState, createRef, useEffect } from 'react';\n\nconst CodeVerificationInputs = ({ onCodeChange, onPaste, onEnter }) => {\n const [inputs, setInputs] = useState(Array(6).fill(''));\n const elements = Array(6)\n .fill()\n .map((_, idx) => createRef());\n\n const focusNext = (idx, str) => {\n if (str && idx < 5) {\n elements[idx + 1].current.focus();\n }\n };\n\n const handleChange = (str, idx) => {\n if (!str || str.match(/[0-9]/)) {\n setInputs(inputs.map((s, index) => (index === idx ? str : s)));\n focusNext(idx, str);\n onCodeChange(idx, str); // Notify the parent component of the change\n }\n };\n\n const handlePaste = e => {\n e.preventDefault();\n const pastedData = e.clipboardData.getData('text');\n if (\n pastedData &&\n pastedData.length === 6 &&\n pastedData.split('').every(char => !Number.isNaN(parseInt(char, 10)))\n ) {\n const pastedArray = pastedData.split('');\n setInputs(pastedArray);\n onPaste(pastedArray); // Pass the pasted array to the parent component\n elements[5].current.focus();\n }\n };\n\n useEffect(() => {\n elements[0].current.focus();\n }, []);\n\n return (\n \n
\n {inputs.slice(0, 3).map((input, idx) => (\n {\n if (e.key === 'Enter') {\n onEnter();\n }\n }}\n onPaste={handlePaste}\n onChange={e => handleChange(e.target.value, idx)}\n maxLength={1}\n style={{ width: '30px', height: '30px', textAlign: 'center', border: '1px solid gray' }}\n ref={elements[idx]}\n />\n ))}\n
\n
\n {inputs.slice(3, 6).map((input, idx) => (\n {\n if (e.key === 'Enter') {\n onEnter();\n }\n }}\n onPaste={handlePaste}\n onChange={e => handleChange(e.target.value, idx + 3)}\n maxLength={1}\n style={{ width: '30px', height: '30px', textAlign: 'center', border: '1px solid gray' }}\n ref={elements[idx + 3]}\n />\n ))}\n
\n
\n );\n};\n\nexport default CodeVerificationInputs;\n","import React, { useEffect, useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { connect } from 'react-redux';\nimport { useSnackbar } from 'notistack';\n\nimport Button from 'shared/components/Button';\nimport { Api } from 'utils/connectors';\nimport { getError } from 'utils/appHelpers';\nimport { loginUser } from '../../../Auth/actions';\n\nimport topLogo from 'assets/ACE-logo-blue.svg';\nimport CodeVerificationInputs from 'app/Auth/components/CodeVerificationInputs';\nimport { TOKEN_RESPONSE_CODES } from 'app/Auth/routes/Register/configs';\nimport EventHeader from '../EventLanding/components/EventHeader';\n\nconst EventEmailVerification = ({\n loginUser,\n history,\n location,\n match,\n title = 'Confirmation Code',\n subTitle = 'Please enter the code which we have sent to your email.',\n className = '',\n}) => {\n const { enqueueSnackbar } = useSnackbar();\n const { name } = match.params;\n const [verificationCode, setVerificationCode] = useState(Array(6).fill(''));\n const [user, setUser] = useState();\n const [isFetching, setIsFetching] = useState(false);\n const [error, setError] = useState();\n const [isCodeExpired, setIsCodeExpired] = useState(false);\n\n const regCode = match.params.code;\n const registeringUserEmail = location?.state?.email;\n const isForgotPassword = location?.state?.isForgotPassword;\n\n const allInputsFilled = verificationCode.every(code => code !== '');\n\n const getUserdata = async code => {\n try {\n const { data } = await Api.get(`/auth/getinviteuser/${code}`);\n if (data.data.invitation.email) {\n setUser({\n fname: data.data.invitation.firstName,\n lname: data.data.invitation.lastName,\n email: data.data.invitation.email,\n });\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n history.push('/login');\n }\n };\n\n const handleCodeChange = (idx, val) => {\n const code = [...verificationCode];\n code[idx] = val;\n setVerificationCode(code);\n };\n\n const handlePaste = pastedArray => {\n setVerificationCode(pastedArray);\n };\n\n const handleResendCode = async () => {\n try {\n await Api.post('/auth/resend-confirmation-code', {\n email: user?.email || registeringUserEmail,\n });\n enqueueSnackbar('Successfully sent!', { variant: 'success' });\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const handleSubmit = async () => {\n try {\n setIsFetching(true);\n if (isForgotPassword) {\n const res = await Api.post('/auth/confirm-forgot-email', {\n email: user?.email || registeringUserEmail,\n code: verificationCode?.join(''),\n });\n if (res.status === 200) {\n history.push({ pathname: `/auth/reset/${res.data.data}` });\n }\n } else {\n const res = await Api.post('/auth/confirm-activation/', {\n email: user?.email || registeringUserEmail,\n code: verificationCode?.join(''),\n });\n await loginUser(res.data, history, `/events/${name}`);\n }\n } catch (err) {\n if (err.response.data.data === TOKEN_RESPONSE_CODES.expiredConfirmationCode) {\n setIsCodeExpired(true);\n }\n setError(getError(err));\n }\n setIsFetching(false);\n };\n\n useEffect(() => {\n if (regCode) getUserdata(regCode);\n }, [regCode]);\n\n return (\n \n
\n
\n
\n
\n
\n \n
\n
\n
\n
{title} \n
{subTitle}
\n
\n
\n {error &&
{error}
}\n {isCodeExpired && (\n
{'Click \"Resend Code\" below to get the new one.'}
\n )}\n
\n \n Submit\n \n
\n
\n \n Resend Code\n \n
\n
\n
\n
\n );\n};\n\nexport default connect(\n null,\n { loginUser },\n)(EventEmailVerification);\n","import Main from 'app/Main';\nimport React from 'react';\nimport { useSelector } from 'react-redux';\nimport { Switch, Redirect } from 'react-router-dom';\nimport EventLanding from './routes/EventLanding';\nimport EventLogin from './routes/EventLogin';\nimport EventRegister from './routes/EventRegister';\nimport { WithTitleRoute, hasAccess } from 'utils/permissionHelper';\nimport EventEmailVerification from './routes/EventEmailVerification';\n\nconst Events = ({ match }) => {\n const isAuthUser = useSelector(state => state.isAuthenticated);\n const isVisitorUser = hasAccess('visitor_user');\n const isAuth = isAuthUser && !isVisitorUser;\n const { path } = match;\n\n return (\n \n \n {!isAuth && }\n {!isAuth && }\n {!isAuth && (\n \n )}\n {isAuth && }\n \n \n \n
\n );\n};\n\nexport default Events;\n","export default __webpack_public_path__ + \"static/media/ACE-logo-soon.9cb91a6e.svg\";","export default __webpack_public_path__ + \"static/media/facebook.e594d652.svg\";","export default __webpack_public_path__ + \"static/media/linkedin.70ef13a9.svg\";","export default __webpack_public_path__ + \"static/media/twitter.92d6a0e7.svg\";","import React from 'react';\nimport logo from 'assets/ACE-logo-soon.svg';\nimport footer from 'assets/events/footer-image.png';\n\nimport soc1 from 'assets/soon/facebook.svg';\nimport soc2 from 'assets/soon/linkedin.svg';\nimport soc3 from 'assets/soon/twitter.svg';\n\nconst ComingSoon = ({ match }) => {\n return (\n \n
\n
\n
\n
\n
\n
\n Thanks for finding us! \n We are not available in your region at the moment\n \n
\n Please visit www.HologicEd.com for additional\n education\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n );\n};\n\nexport default ComingSoon;\n","export default __webpack_public_path__ + \"static/media/toplogo.f440b083.png\";","import React from 'react';\nimport logo from 'assets/promo/toplogo.png';\nimport { Player, BigPlayButton } from 'video-react';\n\nconst links = {\n expromo: 'https://content.hologicace.com/content/expromo1.mp4',\n inpromo: 'https://content.hologicace.com/content/expromo2.mov',\n};\n\nconst PromoPage = ({ history, location }) => {\n const isInpromo = location.pathname.includes('inpromo');\n const videoSettings = {\n src: links[isInpromo ? 'inpromo' : 'expromo'],\n fluid: false,\n preload: 'auto',\n height: window.innerHeight - 160,\n width: '100%',\n };\n\n const onClickAccessACE = () => {\n history.push('/login');\n };\n\n return (\n \n
\n
\n
\n
\n Access ACE\n \n
\n
\n
\n
\n
\n ADVANCED CONTINUING EDUCATION PLATFORM\n
\n
\n
\n );\n};\n\nexport default PromoPage;\n","import React from 'react';\nimport BackButton from 'shared/BackButton';\n\nconst HeaderSection = ({ title, hasBack, onBackClick }) => {\n return (\n \n
\n
\n {hasBack && }\n {title}\n
\n
\n
\n );\n};\nexport default HeaderSection;\n","import React from 'react';\nimport HeaderSection from './HeaderSection';\nimport TypeCasesPlay from 'app/PublicCourseView/CoursePreview/components/TypeCasesPlay';\nimport { generateViewerData } from 'app/Main/routes/Viewer/utils';\n\nconst CaseView = ({ caseData }) => {\n const cases = generateViewerData(caseData, { episodes: [] });\n\n const constructData = {\n isOpened: true,\n title: caseData.title,\n cases,\n };\n\n return (\n \n \n \n
\n );\n};\n\nexport default CaseView;\n","import React, { useEffect, useState } from 'react';\nimport PublicWrapper from 'shared/components/layouts/PublicWrapper';\nimport Loading from 'shared/components/Loading';\nimport { Api } from 'utils/connectors';\nimport CaseView from './CaseView';\n\nconst PublicCaseView = ({ match, history }) => {\n const sharedToken = match.params?.id;\n const [caseData, setCaseData] = useState();\n\n const getCaseData = async () => {\n try {\n const { data } = await Api.get(`/cases/shared/case/${sharedToken}?lut=1&dicomType=all`);\n sessionStorage.setItem('caseSharedToken', sharedToken);\n setCaseData(data.data);\n } catch (err) {\n history.push('/no-web-access');\n }\n };\n\n useEffect(() => {\n getCaseData();\n }, [sharedToken]);\n\n return (\n \n {!caseData ? : }\n \n );\n};\n\nexport default PublicCaseView;\n","import React, { useEffect, useState } from 'react';\nimport PublicWrapper from 'shared/components/layouts/PublicWrapper';\nimport Loading from 'shared/components/Loading';\nimport { Api } from 'utils/connectors';\nimport CoursePreview from './CoursePreview';\nimport logo from 'assets/ace-logo-tsp.png';\nimport TypeVIdeo from 'app/Main/routes/CourseView/components/TypeVIdeo';\nimport TypePDF from 'app/Main/routes/CourseView/components/TypePDF';\nimport TypeCases from 'app/Main/routes/CourseView/components/TypeCases';\nimport TypeImage from 'app/Main/routes/CourseView/components/TypeImage';\nimport { courseSubsStatuses, getError, isMobileDevice } from 'utils/appHelpers';\nimport { formatSubscription } from 'utils/formatHelpers';\nimport { useSnackbar } from 'notistack';\nimport { connect, useSelector } from 'react-redux';\nimport { getCourses } from 'app/Main/routes/Courses/actions';\n\nconst viewsComponents = {\n 1: TypeVIdeo,\n 2: TypePDF,\n 3: TypeCases,\n 4: TypeImage,\n};\n\nconst PublicCourseView = ({ match, history, location, getCourses }) => {\n const { enqueueSnackbar } = useSnackbar();\n const courseIdOrSharedToken = match.params?.id;\n const isCourseIdShareToken = isNaN(courseIdOrSharedToken);\n const needGetCourses = location.state?.needGetCourses;\n const [course, setCourse] = useState();\n const [subscription, setSubscription] = useState(null);\n\n const userId = useSelector(item => item?.account?.id);\n\n const fetchSubs = async () => {\n try {\n const subs = course?.subscriptionPlans && course?.subscriptionPlans[0];\n if (subs) {\n const { data } = await Api.get(`subscription/getsubscription/${subs.subscriptionId}`);\n setSubscription(formatSubscription(data.data));\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const autoEnroll = async () => {\n try {\n await Api.post('/subscription/subscribe', {\n subscriptionId: subscription?.id,\n userId,\n courseId: course?.id,\n });\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const getSharedCourseData = async () => {\n try {\n const { data } = await Api.get(`/courses/shared/course/${courseIdOrSharedToken}`);\n sessionStorage.setItem('courseSharedToken', courseIdOrSharedToken);\n setCourse(data.data);\n } catch (err) {\n history.push('/no-web-access');\n }\n };\n\n const getPublicCourseData = async () => {\n try {\n const { data } = await Api.get(`/courses/getcourse/${courseIdOrSharedToken}`);\n setCourse(data.data);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n history.push('/allcourses');\n }\n };\n\n const onCasePlayClick = () => {};\n\n useEffect(() => {\n isCourseIdShareToken ? getSharedCourseData() : getPublicCourseData();\n }, [courseIdOrSharedToken]);\n\n useEffect(() => {\n if (course) {\n fetchSubs();\n }\n }, [course]);\n\n useEffect(() => {\n if (\n course?.isMarketingMaterial &&\n !courseSubsStatuses(course?.userCourse?.userCourseStatus, 'active')\n ) {\n autoEnroll();\n }\n }, [subscription]);\n\n useEffect(() => {\n if (needGetCourses) getCourses();\n }, [needGetCourses]);\n\n const LessonView =\n course?.courseLessons?.[0]?.lesson &&\n viewsComponents[(course?.courseLessons?.[0]?.lesson?.contentType)];\n\n if (course?.isMarketingMaterial && isMobileDevice()) {\n return (\n \n
\n
\n
\n
\n
\n
\n
\n {!!course?.courseLessons?.[0]?.lesson && (\n
\n )}\n
\n );\n }\n\n return (\n \n {!course ? : }\n \n );\n};\n\nconst mapStateToProps = state => ({\n courses: state.courses,\n});\n\nconst mapDispatchToProps = {\n getCourses,\n};\n\nexport default connect(\n mapStateToProps,\n mapDispatchToProps,\n)(PublicCourseView);\n","import React from 'react';\nimport { Link } from 'react-router-dom';\nimport blueLogo from 'assets/ACE-logo-blue.svg';\n\nconst LinkInfo = ({ title, link, name }) => (\n \n {title} \n \n {name}\n \n
\n);\n\nconst AuthBlock = ({\n title,\n subTitle,\n children,\n links,\n successBlock,\n noFooterInfo,\n large,\n subTitleClassName = 'opc-3',\n}) => {\n return (\n \n
\n
\n
\n \n {successBlock ? (\n successBlock\n ) : (\n <>\n {title &&
{title} }\n {subTitle &&
{subTitle}
}\n
{children}
\n >\n )}\n
\n {!noFooterInfo && (\n
\n {links.reset && }\n {links.signup && (\n \n )}\n {links.signin && (\n \n )}\n
\n )}\n
\n );\n};\n\nexport default AuthBlock;\n","import React, { useState } from 'react';\nimport { connect } from 'react-redux';\nimport InputGroup from 'shared/components/InputGroup';\nimport { loginUser } from './../actions/index';\nimport { Api } from 'utils/connectors';\nimport { getError } from 'utils/appHelpers';\nimport ButtonLine from 'shared/components/ButtonLine';\nimport AuthBlock from '../components/AuthBlock';\nimport { IconMail, IconPassword } from 'shared/components/Icons';\nimport AuthPage from '../components/AuthPage';\n\nconst Login = ({ loginUser, history, location }) => {\n const [error, setError] = useState();\n const [fetch, setFetch] = useState();\n const [auth, setAuth] = useState({\n email: '',\n password: '',\n });\n\n const authOptions = {\n title: <>Welcome to ACE>,\n links: { signup: true, reset: true },\n };\n\n const handleSubmit = async e => {\n e.preventDefault();\n setFetch(true);\n try {\n const res = await Api.post('/auth/login', auth);\n if (res.data.data.pendingConfirmation) {\n history.push({ pathname: 'email-verification', state: { email: auth.email } });\n return;\n }\n await loginUser(res.data, history, location.state?.loginForwardUrl);\n setFetch(false);\n } catch (err) {\n setError(getError(err));\n setFetch(false);\n }\n };\n\n const handleChange = ({ target }) => {\n const { name, value } = target;\n const tempAuth = { ...auth };\n tempAuth[name] = value;\n setAuth(tempAuth);\n };\n\n return (\n \n \n \n }\n autoFocus\n required\n />\n }\n required\n />\n {error && {error}
}\n \n Sign In\n \n \n \n \n );\n};\n\nexport default connect(\n null,\n { loginUser },\n)(Login);\n","import React, { useState } from 'react';\nimport InputGroup from 'shared/components/InputGroup';\nimport { Api } from 'utils/connectors';\nimport { getError } from 'utils/appHelpers';\nimport AuthBlock from '../components/AuthBlock';\nimport { IconMail } from 'shared/components/Icons';\nimport Button from 'shared/components/Button';\nimport AuthPage from '../components/AuthPage';\n\nconst Forgot = ({ history }) => {\n const [error, setError] = useState();\n const [fetch, setFetch] = useState();\n const [email, setEmail] = useState('');\n\n const authOptions = {\n title: 'Password reset',\n subTitle: 'Please enter your email, so we can send you a code to reset your password',\n links: { signup: true },\n subTitleClassName: 'fsize_18',\n };\n\n const handleSubmit = async e => {\n e.preventDefault();\n setFetch(true);\n try {\n const res = await Api.post(`/auth/forgot`, { email });\n if (res.status === 200) {\n history.push({ pathname: '/email-verification', state: { email, isForgotPassword: true } });\n }\n } catch (err) {\n setError(getError(err));\n } finally {\n setFetch(false);\n }\n };\n\n return (\n \n \n \n setEmail(e.target.value)}\n className='mb-4'\n placeholder='Email address'\n value={email}\n icon={ }\n autoFocus\n required\n />\n {error && {error}
}\n \n Send\n \n \n \n \n );\n};\n\nexport default Forgot;\n","import React from 'react';\nimport { Link } from 'react-router-dom';\nimport ButtonLine from 'shared/components/ButtonLine';\nimport blueLogo from 'assets/ACE-logo-blue.svg';\nimport AuthPage from '../components/AuthPage';\n\nconst UserLicense = ({ history }) => {\n return (\n \n \n
\n
\n
\n \n
\n
\n
\n
End-User Software License Agreement
\n
\n
\n
Last updated: August 20, 2020
\n Please read this End-User Software License Agreement (hereinafter the “Agreement”)\n carefully before clicking the “I Agree” button, downloading or using the software\n (hereinafter the “Licensed Software”).\n
\n
\n By clicking the “I Agree” button, downloading or using the Licensed Software, You are\n agreeing to be bound by the terms and conditions of this Agreement. If You are accepting\n these terms of behalf of another person or company or other legal entity, You represent\n and warrant that you have full authority to bind that person, company or legal entity to\n these terms.\n
\n
\n If You do not agree to the terms of this Agreement, do not click on the “I Agree” button\n and You cannot download or use the Licensed Software.\n
\n
\n “You” or “Your” means the person or entity who is being licensed to use the Licensed\n Software in association with any sales or service agreement (“Usage Agreement”). “We”,\n “Our” and “Us” means Hologic, Inc.\n
\n
\n 1. License Grant. We grant You a revocable, non-exclusive, non-transferable, limited\n right to access and use the Licensed Software on a device owned and controlled by You,\n and to access and use the Licensed Software strictly in accordance with the terms and\n conditions of this Agreement, the Usage Agreement and any service agreement, sales\n agreement or any other agreement with You, (collectively, “Related Agreements”).\n
\n
\n 2. Restrictions on Use. You shall use the Licensed Software strictly in accordance with\n the terms of the Related Agreements and shall not: (a) decompile, reverse engineer,\n disassemble, attempt to derive the source code of, maliciously penetrate any system or\n network which runs or stores, or decrypt the Licensed Software; (b) make any\n modification, automation, conversion, adaptation, improvement, enhancement, translation\n or derivative work to or from the Licensed Software; (c) remove, circumvent or create,\n or use any workaround to any copy protection or security feature relating to the\n Licensed Software; (d) violate any applicable laws, rules or regulations in connection\n with Your access or use of the Licensed Software; (e) remove, alter, or obscure any\n proprietary notice (including any notice of copyright or trademark) of Ours or Our\n affiliates, partners, suppliers or licensors; (f) use the Licensed Software for any\n revenue generating endeavor, commercial enterprise, or other purpose for which it is not\n designed or intended; (g) install, use or permit the Licensed Software to be used only\n on the originally designated Equipment for the Licensed Software; (h) make the Licensed\n Software available over a network or other environment permitting access or use by\n multiple devices or users at the same time; (i) use the Licensed Software for creating a\n product, service or software that is, directly or indirectly, competitive with or in any\n way a substitute for any product, services or software offered by Us; (j) use the\n Licensed Software to send automated queries to any website or to send any unsolicited\n commercial e-mail; (k) connect the Licensed Software to any online service or other\n applications, software or services not provided or permitted by Us; (l) use any\n proprietary information or interfaces of Ours or other intellectual property of Ours in\n the design, development, manufacture, licensing or distribution of any applications,\n accessories or devices for use with the Licensed Software; (m) own title, or transfer\n title to the Licensed Software to any third party; (n) distribute, or sublicense or\n otherwise provide copies or any rights in relation to the Licensed Software to any third\n party; or (o) pledge, hypothecate, alienate or otherwise encumber the Licensed Software\n to any third party.
\n
3. Consent to Use of Content. You agree that We may collect and use device data\n and related information, including but not limited to technical information about Your\n devices, products, system and application software, and peripherals, that is gathered to\n facilitate the provision of software updates, product support and other services to You\n related to the Licensed Software (collectively, “Data”). You may also have agreed to\n provide information, feedback, de-identified images, and other content in connection\n with Your use of the Licensed Software (together with the Data, collectively,\n “Content”). You agree that We may use the Content to improve the products and services\n or to provide products and services to You, and You hereby grant Us a non-exclusive,\n worldwide, royalty-free, fully paid-up, transferable and sub- licensable license in and\n to the Content, including all intellectual property rights therein, for Us to use,\n modify and create derivative works of the same in connection with or related to any\n business purposes. You represent and warrant to Us that (i) you have the necessary\n rights to grant the licenses and rights in the Content, and (ii) the Content and Our use\n thereof as permitted in this Agreement will not infringe, violate or misappropriate any\n third party right. Content will not include any patient health information (“PHI”). For\n the avoidance of doubt, "Protected Health Information" or "PHI"\n shall mean individually identifiable health information regardless of the form in which\n it is maintained or transmitted. We may, at our sole discretion, aggregate and\n de-identify any and all PHI obtained under this Agreement and use all such de-identified\n data in accordance with the de-identification requirements at 45 CFR 164.514(b). With\n respect to de-identification, the aggregated and de-identified data We produce or use\n either: (i) will not include any identifiers listed in 45 CFR 164.514(b)(2)(i), or (ii)\n will have been determined by a person with appropriate knowledge of and experience with\n generally accepted statistical and scientific principles and methods for rendering\n information not individually identifiable and applying such principles and methods, that\n the risk is very small that the aggregated and de-identified data generated under this\n Agreement could be used, alone or in combination with other reasonably available\n information, by an anticipated recipient, to identify an individual who is a subject of\n the information, thereby forming a "statistically de-identified data set" and\n rendering the information not PHI under HIPAA. De-identified information does not\n constitute PHI and is not subject to the terms of this Agreement.\n
\n
4. Intellectual Property Rights. You acknowledge and agree that the Licensed\n Software and Our documentation are provided under license, and not sold, to You. You do\n not acquire any ownership interest in the Licensed Software or Our documentation under\n this Agreement, or any other rights thereto, other than to use the Licensed Software in\n accordance with Our documentation, subject to all of the terms, conditions and\n restrictions under this Agreement and any Usage Agreement. We reserve and shall retain\n all right, title and interest in and to the Licensed Software and all intellectual\n property rights arising out of or relating to the Licensed Software, except as expressly\n granted to You in this Agreement. You shall safeguard the Licensed Software from\n infringement, misappropriation, theft, misuse or unauthorized access. You shall promptly\n notify Us if You become aware of any infringement of Our intellectual property rights in\n the Licensed Software and fully cooperate with Us in any legal action taken by Us to\n enforce Our intellectual property rights. Further, We will be free to use any ideas,\n concepts, know-how or techniques contained in such Content for any purpose whatsoever,\n including, without limitation, developing, making, marketing, distributing and selling\n products and services incorporating such Content. We will have no obligation to\n consider, use, return or preserve any of the Content you provide to Us. Any Content You\n provide to Us may or may not be treated confidentially by Us, and We will have no\n obligation or liability to You for the use or disclosure of any of the Content. You\n should not to expect any compensation of any kind from Us with respect to the Content\n You provided.
\n
\n 5. Limited Warranty and Disclaimer.\n
\n a) Limited Warranty. We warrant that, for a period of ninety (90) days following the\n date the Licensed Software is made available to You for Your use, the Licensed Software\n will perform substantially in accordance with Our documentation (the “Limited\n Warranty”).\n
b) Exclusive Remedy. In case of any breach of the above Limited Warranty, as your\n exclusive remedy and Our entire obligation and liability, We will: (i) repair or replace\n the Licensed Software; or (ii) if such repair or replacement would in Our opinion be\n commercially unreasonable, upon Our receipt of Your written representation and promise\n that you have removed all instances of the Licensed Software and will not use the\n Licensed Software, refund the price paid by You for the applicable Licensed Software.{' '}\n
\n c) Exclusion of Warranty. THE ABOVE LIMITED WARRANTY WILL NOT APPLY IF: (i) THE LICENSED\n SOFTWARE IS NOT USED IN ACCORDANCE WITH THIS AGREEMENT OR OUR DOCUMENTATION; (ii) THE\n LICENSED SOFTWARE OR ANY PART THEREOF HAS BEEN MODIFIED BY YOU OR ANY ENTITY OTHER THAN\n US; OR (iii) A MALFUNCTION IN THE LICENSED SOFTWARE HAS NOT BEEN CAUSED BY ANY EQUIPMENT\n OR SOFTWARE NOT APPROVED OR SUPPLIED BY US.
\n d) Disclaimer. EXCEPT FOR THE LIMITED WARRANTY SET FORTH ABOVE, THE LICENSED SOFTWARE IS\n PROVIDED “AS IS” AND WE MAKE NO REPRESENTATIONS OR WARRANTIES, AND WE DISCLAIM ALL\n REPRESENTATIONS, WARRANTIES AND CONDITIONS, ORAL OR WRITTEN, EXPRESS OR IMPLIED, ARISING\n FROM COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE IN TRADE, OR OTHERWISE,\n INCLUDING, WITHOUT LIMITATION, IMPLIED WARRANTIES OF MERCHANTABILITY, QUALITY, FITNESS\n FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, OR SYSTEMS INTEGRATION. WITHOUT\n LIMITING THE FOREGOING, WE MAKE NO WARRANTY, REPRESENTATION, OR GUARANTEE THAT THE\n OPERATION OF THE LICENSED SOFTWARE WILL BE FAILSAFE, UNINTERRUPTED, OR FREE FROM ERRORS\n OR DEFECTS.
\n
\n 6. Limitation of Liability. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT SHALL WE BE\n LIABLE FOR PERSONAL INJURY, OR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL\n DAMAGES WHATSOEVER, INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF PROFITS, LOSS OF\n DATA, BUSINESS INTERRUPTION OR ANY COMMERCIAL DAMAGES OR LOSSES, ARISING OUT OF OR\n RELATED TO YOUR USE OR INABILITY TO USE THE LICENSED SOFTWARE, HOWEVER CAUSED,\n REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT, TORT (INCLUDING NEGLIGENCE), OR\n OTHERWISE) AND EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME\n JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY FOR PERSONAL INJURY, OR OF\n INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY TO YOU. IN NO\n EVENT SHALL OUR TOTAL LIABILITY TO YOU FOR ALL DAMAGES EXCEED THE LICENSED SOFTWARE FEE\n PAID BY YOU FOR THE APPLICABLE TERM. NOTHING IN THIS AGREEMENT LIMITS OR EXCLUDES ANY\n LIABILITY THAT CANNOT BE LIMITED OR EXCLUDED UNDER APPLICABLE LAW.\n
\n
\n 7. Intellectual Property Indemnity.\n
a) Indemnity. We will indemnify, and at our election, defend, You against claims\n asserted against You in a suit or action if: (i) the claim is for direct patent\n infringement or direct copyright infringement, or for misappropriation of third party\n trade secrets; (ii) the claim is asserted against the Licensed Software, alone and not\n in combination with any third party software or systems.\n
\n b) Exclusions. Notwithstanding anything else in this Agreement, We have no obligation to\n indemnify or defend you for claims asserted, in whole or in part, if (i) the Licensed\n Software is not used in accordance with this Agreement or Our documentation; (ii) the\n Licensed Software or any part thereof has been modified by You or any entity other than\n Us; or (iii) a malfunction in the Licensed Software has been caused by any equipment or\n software not approved or supplied by Us.
\n c) Conditions. As a condition of Our obligations under this Section 7, You must provide\n Us with: (i) prompt written notice of the claim and Your agreement to give Us sole\n control over the defense and settlement of the claim; and Your full and timely\n cooperation. We will not be responsible for any costs, expenses or compromises that You\n make or incur without Our written consent.\n
d) Remedies. We may, at Our sole discretion and expense: (i) procure for you the\n right to continue using the Licensed Software; (ii) replace the Licensed Software with\n non-infringing Licensed Software; (iii) modify the Licensed Software so that it becomes\n non-infringing; or (iv) upon Your return of the Licensed Software to Us, and/or removal\n of the Licensed Software from your systems, refund the residual value of the Licensed\n Software fee paid by You for the infringing Licensed Software, depreciated using a\n straight-line method of depreciation over a three (3) year period from the date of\n delivery of the Licensed Software to You. This indemnity section states Our entire\n obligation and Your exclusive remedy for claims of patent or copyright infringement, or\n trade secret misappropriation, made in whole or in part against the Licensed Software.\n
\n
8. Export. You may not use or otherwise export or re-export the Licensed Software\n except as authorized by United States law and the laws of the jurisdiction in which the\n Licensed Software was obtained. In particular, but without limitation, the Licensed\n Software may not be exported or re-exported: (i) into any U.S. embargoed countries; or\n (ii) to anyone on the U.S. Treasury Department’s list of Specially Designated Nationals\n or the U.S. Department of Commerce Denied Person’s List or Entity List. By using the\n Licensed Software, You represent and warrant that You are not located in any such\n country or on any such list and that You will not use the Licensed Software for any\n purposes prohibited by United States law.
\n
\n 9. Term. The term of this Agreement will commence on the date of Your agreement to these\n terms and shall continue for the same term as the Usage Agreement. Hologic may terminate\n this Agreement at any time for any reason, including to the extent that any Usage\n Agreement has been terminated or expired.
\n
\n 10. General. a) Governing Law. Jurisdiction. Upon acceptance of these terms, this\n Agreement is governed by the laws of the Commonwealth of Massachusetts, without regard\n to any conflict of law principles to the contrary. You hereby irrevocably consent to\n jurisdiction of the state and federal courts located in Boston, Massachusetts with\n respect to any proceeding regarding this Agreement or the Licensed Software. The 1980 UN\n Convention for the International Sale of Goods or any successor thereto does not apply\n to this Agreement. You will not prosecute any action, suit, proceeding or claim arising\n under or by reason of this Agreement or the Licensed Software except in such courts.\n
b) Severability. If any provision of this Agreement is held by a court of\n competent jurisdiction to be invalid or unenforceable, the remainder of this Agreement\n will remain in full force and effect, and the remaining provisions will be amended to\n achieve as closely as possible the effect of the original term and all other provisions\n of this Agreement will continue in full force.\n
c) Assignment. You may not assign or otherwise transfer the Licensed Software or\n this Agreement, or assign, sub-license or otherwise transfer to any other person or\n entity any of Your rights under this Agreement without Our prior written consent, and\n any attempted assignment without such consent will be void.
\n d) Notices. All "notices" provided by You under this Agreement shall be in\n writing sent to the contact information associated with the user account to access the\n Licensed Software or other means providing proof of delivery.
\n e) Revisions to this Agreement. We may at any time revise the terms of this Agreement by\n updating these terms and by providing notice to You of that change upon You accessing\n the Licensed Software at the time of said change and agreeing to the revised terms.{' '}\n
\n
\n 11. Entire Agreement. This Agreement together with the Related Agreements sets forth Our\n entire agreement with respect to Your use of the Licensed Software and supersedes all\n prior and contemporaneous understandings and agreements with respect to the Licensed\n Software and Your use thereof whether written or oral. In the event of a conflict, the\n Usage Agreement shall supersede and control over the terms of this End-User Software\n License Agreement.\n
\n
\n 12. General Disclaimers. This is a general information tool for medical professionals\n and is not a complete representation of the product(s)’ Instruction for Use (IFU) or\n Package Insert, and it is the medical professionals’ responsibility to read and follow\n the IFU or Package Insert. The information provided may suggest a particular technique\n or protocol however it is the sole responsibility of the medical professional to\n determine which technique or protocol is appropriate. At all times, clinicians remain\n responsible for utilizing sound patient evaluation and selection practices, and for\n complying with applicable local, state, and federal rules and regulations regarding\n accreditation, anesthesia, reimbursement, and all other aspects of in- office\n procedures. In no event shall Hologic be liable for damages of any kind resulting from\n your use of the information presented. This information is intended for medical\n professionals in the U.S. and other markets and is not intended as a product\n solicitation or promotion where such activities are prohibited. Because Hologic\n materials are distributed through websites, eBroadcasts, and tradeshows, it is not\n always possible to control where such materials appear. For specific information on what\n products are available for sale in a particular country, please contact your local\n Hologic representative or write to womenshealth@hologic.com
\n
13. Medical Use Disclaimer. This training program is not intended for clinical or\n diagnostic use.
\n
\n 14. Faculty Statement Disclaimer. Views and opinions expressed herein by third parties\n are theirs alone and do not necessarily reflect those of Hologic. 15. Copyright.\n Copyright© 2020, Hologic. Inc. Reproduction, display or copying of this information\n without express written permission is strictly prohibited. All materials, images, and\n content are intellectual property of Hologic and may not be copied, reproduced,\n distributed, or displayed without permission.\n
\n
\n history.goBack()} className='p-0'>\n Done\n \n
\n
\n
\n \n );\n};\n\nexport default UserLicense;\n","import React from 'react';\nimport { Link } from 'react-router-dom';\n\nconst SuccessInfo = ({ title, subTitle, className, signinUrl, subTitleClassName }) => {\n return (\n \n
\n
{title} \n
{subTitle}
\n
\n
\n \n Sign In\n \n
\n
\n );\n};\n\nexport default SuccessInfo;\n","import React, { useState } from 'react';\nimport InputGroup from 'shared/components/InputGroup';\nimport { Api } from 'utils/connectors';\nimport { getError } from 'utils/appHelpers';\nimport AuthBlock from '../components/AuthBlock';\nimport { IconPassword } from 'shared/components/Icons';\nimport SuccessInfo from '../components/SuccessInfo';\nimport Button from 'shared/components/Button';\nimport AuthPage from '../components/AuthPage';\n\nconst Reset = ({ match }) => {\n const [success, setSuccess] = useState(false);\n const [error, setError] = useState();\n const [fetch, setFetch] = useState();\n const [pass, setPass] = useState({\n password: '',\n rpassword: '',\n });\n\n const authOptions = {\n title: 'New password',\n subTitle: 'Please enter new password.',\n links: { signup: true },\n subTitleClassName: '',\n };\n\n const passwordMatch = pass.password === pass.rpassword;\n\n const handleChange = ({ target }) => {\n const { name, value } = target;\n const tempAuth = { ...pass };\n tempAuth[name] = value;\n setPass(tempAuth);\n };\n\n const handleSubmit = async e => {\n e.preventDefault();\n setFetch(true);\n try {\n const body = {\n resetToken: match.params.code,\n password: pass.password,\n };\n await Api.post(`/auth/reset-password`, body);\n setSuccess(true);\n } catch (err) {\n setError(getError(err));\n } finally {\n setFetch(false);\n }\n };\n\n return (\n \n \n ) : null\n }\n >\n \n }\n autoFocus\n required\n />\n }\n required\n />\n {error && {error}
}\n {!passwordMatch && pass.rpassword && Passwords mismatch
}\n \n Submit\n \n \n \n \n );\n};\n\nexport default Reset;\n","/* eslint-disable react/jsx-no-target-blank */\nimport React, { useState } from 'react';\nimport ButtonLine from 'shared/components/ButtonLine';\nimport { IconInfo } from 'shared/components/Icons';\nimport { onOpenExternalUrl } from 'utils/appHelpers';\n\nconst InfoStep = ({ onNextClick }) => {\n const [checkConfirm, setCheckConfirm] = useState(false);\n const [checkConfirm2, setCheckConfirm2] = useState(false);\n return (\n \n
\n
Important Information
\n
\n
\n
\n
\n
\n Upon activation of your registration, you will receive an e-mail to confirm your new\n account. Please open that email and verify, before logging in for the first time.\n
\n
Accessing the ACE Portal:
\n
\n \n Hardware:\n \n Standard laptop, desktop computer or iPad \n Resolution of at least 1366×768 \n \n \n \n Internet Connection:\n \n No firewall blocking ACE domain (hologicace.com) \n Bandwidth 7 MBits/s connection \n \n \n \n Browser:\n \n \n Google Chrome (min version 79.0.3945 )\n \n \n Mozilla Firefox (min version 78.0 )\n \n \n Safari (Mac) (min version 13 )\n \n \n Microsoft Edge (min version 79.0.309 )\n \n Internet Explorer is no longer supported \n \n \n \n
\n
\n
\n
\n
\n );\n};\n\nexport default InfoStep;\n","import React, { useState } from 'react';\nimport InputGroup from 'shared/components/InputGroup';\nimport { countries, degrees, specialty, states } from '../configs';\nimport Select from 'shared/components/Select';\nimport Button from 'shared/components/Button';\n\nconst FormStep = ({ formSubmit, user, fetch, error, regCode, lastForm, isEventUser }) => {\n const intial = {\n fname: '',\n lname: '',\n email: '',\n institution: '',\n countryId: '',\n city: '',\n address: '',\n phone: '',\n zipCode: '',\n degree: '',\n specialty: '',\n prefferedNameOnCertificate: '',\n password: '',\n rpassword: '',\n state: '',\n ...user,\n ...lastForm,\n };\n const [info, setInfo] = useState(intial);\n\n const areAllRequiredFieldsFilled = () => {\n const requiredFields = [\n 'fname',\n 'lname',\n 'degree',\n 'specialty',\n 'email',\n 'password',\n 'rpassword',\n 'institution',\n 'countryId',\n 'city',\n 'state',\n 'zipCode',\n 'address',\n 'phone',\n ];\n return requiredFields.every(field => Boolean(info[field]));\n };\n\n const isCountryUs = info.countryId === '226';\n const passwordMatch = info.password === info.rpassword;\n\n const handleChange = ({ target }) => {\n const { name, value } = target;\n const tempAuth = { ...info };\n tempAuth[name] = value;\n setInfo(tempAuth);\n };\n\n const handleFormSubmit = e => {\n e.preventDefault();\n const body = {\n regCode,\n email: info.email,\n password: isEventUser ? undefined : info.password,\n firstname: info.fname,\n lastname: info.lname,\n usertype: 1,\n countryid: info.countryId,\n learnerprofile: {\n state: info.state,\n institution: info.institution,\n city: info.city,\n address: info.address,\n zipCode: info.zipCode,\n phone: info.phone,\n degree: info.degree,\n profession: info.specialty,\n },\n };\n if (info.prefferedNameOnCertificate) {\n body.learnerprofile.prefferedNameOnCertificate = info.prefferedNameOnCertificate;\n }\n formSubmit(body, info);\n };\n\n return (\n \n Personal Information \n \n \n \n
\n \n \n \n
\n \n \n
\n \n \n
\n \n \n \n
\n Institute Information \n \n \n
\n \n \n {isCountryUs ? (\n \n ) : (\n \n )}\n \n
\n \n \n
\n \n \n
\n \n {!passwordMatch && info.rpassword &&
Passwords mismatch
}\n {error &&
{error}
}\n
\n Next\n \n
\n \n );\n};\n\nexport default FormStep;\n","import React from 'react';\nimport AuthBlock from './AuthBlock';\nimport Button from 'shared/components/Button';\n\nconst InfoComponent = ({ className = '', title, subtitles, actionText, actionHandler }) => {\n return (\n \n
\n
{title} \n {subtitles &&\n subtitles.map((subtitle, idx) => (\n
\n {subtitle}\n
\n ))}\n
\n
\n \n {actionText}\n \n
\n
\n );\n};\n\nconst InformativeMessageComponent = ({\n match,\n history,\n title,\n subtitles,\n actionText,\n actionHandler,\n}) => {\n const Info = {\n title,\n subtitles,\n actionText,\n actionHandler,\n };\n\n const authOptions = {\n links: { signin: true },\n };\n\n return (\n }\n />\n );\n};\n\nexport default InformativeMessageComponent;\n","import React, { useState, useEffect } from 'react';\nimport { useSelector } from 'react-redux';\nimport { Link } from 'react-router-dom';\n\nimport { TOKEN_RESPONSE_CODES } from './configs';\nimport { Api } from 'utils/connectors';\nimport { getError } from 'utils/appHelpers';\n\nimport InfoStep from './components/InfoStep';\nimport FormStep from './components/FormStep';\nimport InformativeMessageComponent from 'app/Auth/components/InformativeMessage';\n\nimport { IconLeft } from 'shared/components/Icons';\nimport blueLogo from 'assets/ACE-logo-blue.svg';\nimport AuthPage from 'app/Auth/components/AuthPage';\n\nconst Register = ({ history, match }) => {\n const isAuth = useSelector(state => state.isAuthenticated);\n const account = useSelector(state => state.account);\n const [error, setError] = useState();\n const [fetch, setFetch] = useState(false);\n const [step, setStep] = useState(1);\n const [user, setUser] = useState({});\n const [form, setForm] = useState({});\n const [isTokenIncorrect, setIsTokenIncorrect] = useState(false);\n const [isTokenExpired, setIsTokenExpired] = useState(false);\n const [isAlreadyRegistered, setIsAlreadyRegistered] = useState(false);\n const regCode = match.params.code;\n const isEventUser = isAuth && account && account.userType === 6;\n\n const incorrectInfoSectionDetails = {\n title: 'Incorrect Link Token',\n subtitles: [\n `The link you are trying to access is incorrect.`,\n `Please check your email and copy the correct full code.`,\n ],\n actionText: 'Go to Sign in',\n actionHandler: () => history.push('/login'),\n };\n\n const expiredInfoSectionDetails = {\n title: 'Your Link is Expired',\n subtitles: [`Please contact with administrator to get new link.`],\n actionText: 'Contact with admin',\n actionHandler: () => {},\n };\n\n const registeredInfoSectionDetails = {\n title: 'You are already registered',\n subtitles: ['Please click to sign in and enter your email and password.'],\n actionText: 'Sign in',\n actionHandler: () => history.push('/login'),\n };\n\n const handleFormSubmit = async (data, form) => {\n setFetch(true);\n try {\n setForm({ ...form, email: '', password: '', rpassword: '' });\n await Api.post('/auth/register', data);\n history.push({\n pathname: `/email-verification/${regCode || ''}`,\n state: { email: form.email },\n });\n } catch (err) {\n setError(getError(err));\n } finally {\n setFetch(false);\n }\n };\n\n const handleStepChange = bool => {\n if (bool) {\n setStep(step + 1);\n return;\n }\n step === 1 ? history.goBack() : setStep(step - 1);\n };\n\n const getUserdata = async code => {\n try {\n setFetch(true);\n const { data } = await Api.get(`/auth/getinviteuser/${code}`);\n if (data.data.pendingConfirmation) {\n history.push({ pathname: `/email-verification/${regCode}` });\n }\n setUser({\n fname: data.data.invitation.firstName,\n lname: data.data.invitation.lastName,\n email: data.data.invitation.email,\n degree: data.data.invitation.degree,\n });\n if (data.data.pendingConfirmation === false && data.data.pendingRegistration === false) {\n setIsAlreadyRegistered(true);\n }\n } catch (err) {\n if (err.response.data.result === TOKEN_RESPONSE_CODES.invalidInvitationToken) {\n setIsTokenIncorrect(true);\n } else if (err.response.data.result === TOKEN_RESPONSE_CODES.expiredInvitationToken) {\n setIsTokenExpired(true);\n }\n }\n setFetch(false);\n };\n\n const setEventUserData = () => {\n setUser({\n fname: account.firstName,\n lname: account.lastName,\n email: account.email,\n institution: account.learnerProfile.institution,\n });\n };\n\n useEffect(() => {\n if (regCode) getUserdata(regCode);\n if (isEventUser) setEventUserData();\n //eslint-disable-next-line\n }, [regCode]);\n\n return (\n \n {isAlreadyRegistered && }\n {isTokenIncorrect && }\n {isTokenExpired && }\n {!isTokenIncorrect && !isTokenExpired && !isAlreadyRegistered && (\n \n {step < 3 && (\n
\n \n \n Back \n \n
\n )}\n
\n
\n
\n \n
\n {step === 1 &&
}\n {step === 2 && (\n
\n )}\n
\n )}\n \n );\n};\n\nexport default Register;\n","import React, { useState, useEffect, useCallback } from 'react';\nimport { useSnackbar } from 'notistack';\nimport { Api } from 'utils/connectors';\nimport AuthBlock from '../components/AuthBlock';\nimport SuccessInfo from '../components/SuccessInfo';\nimport Loading from 'shared/components/Loading';\nimport { getError } from 'utils/appHelpers';\nimport AuthPage from '../components/AuthPage';\n\nconst Verify = ({ match, history }) => {\n const { enqueueSnackbar } = useSnackbar();\n const [verifyData, setVerifyData] = useState({});\n const [validCode, setValidCode] = useState(false);\n\n const signinUrl =\n verifyData && verifyData.eventId ? `/events/${verifyData.eventId}/login` : '/login';\n\n const successInfo = {\n title: 'Thank you!!',\n subTitle: 'Your email has been successfully verified',\n signinUrl,\n subTitleClassName: 'opc-3',\n };\n\n const authOptions = {\n links: {\n signin: true,\n signinUrl,\n },\n };\n\n const checkVerifyCode = useCallback(async () => {\n const code = match.params.code;\n try {\n const { data } = await Api.get(`/auth/verify/${code}`);\n setVerifyData(data.data);\n setValidCode(true);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n history.push('/login');\n }\n }, [enqueueSnackbar, history, match.params.code]);\n\n useEffect(() => {\n checkVerifyCode();\n }, [checkVerifyCode]);\n\n if (!validCode) return ;\n\n return (\n \n } />\n \n );\n};\n\nexport default Verify;\n","import React from 'react';\nimport InformativeMessageComponent from '../components/InformativeMessage';\nimport AuthPage from '../components/AuthPage';\n\nconst NoAccess = ({ match, history }) => {\n const info = {\n title: 'No Permission',\n subtitles: [\n `Your account doesn't have permission to access this area.`,\n `If you have an account with such permissions Sign in using other account.`,\n ],\n actionText: 'Sign in',\n actionHandler: () => history.push('/login'),\n };\n\n return (\n \n \n \n );\n};\n\nexport default NoAccess;\n","import React from 'react';\n\nimport InformativeMessageComponent from '../components/InformativeMessage';\nimport AuthPage from '../components/AuthPage';\n\nconst IncorrectLink = ({ match, history }) => {\n const info = {\n title: 'No Permission',\n subtitles: [\n `Your account doesn't have permission to access this area.`,\n `If you have an account with such permissions Sign in using other account.`,\n ],\n actionText: 'Sign in',\n actionHandler: () => history.push('/login'),\n };\n\n return (\n \n \n \n );\n};\n\nexport default IncorrectLink;\n","import React, { useEffect, useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { connect } from 'react-redux';\nimport { useSnackbar } from 'notistack';\n\nimport Button from 'shared/components/Button';\nimport { Api } from 'utils/connectors';\nimport CodeVerificationInputs from '../components/CodeVerificationInputs';\nimport { getError } from 'utils/appHelpers';\nimport { loginUser } from '../actions';\nimport { TOKEN_RESPONSE_CODES } from './Register/configs';\n\nimport topLogo from 'assets/ACE-logo-blue.svg';\nimport AuthPage from '../components/AuthPage';\n\nconst EmailVerification = ({\n loginUser,\n history,\n location,\n match,\n title = 'Confirmation Code',\n subTitle = 'Please enter the code which we have sent to your email.',\n className = '',\n}) => {\n const { enqueueSnackbar } = useSnackbar();\n const [verificationCode, setVerificationCode] = useState(Array(6).fill(''));\n const [user, setUser] = useState();\n const [isFetching, setIsFetching] = useState(false);\n const [error, setError] = useState();\n const [isCodeExpired, setIsCodeExpired] = useState(false);\n\n const regCode = match.params.code;\n const registeringUserEmail = location?.state?.email;\n const isForgotPassword = location?.state?.isForgotPassword;\n\n const allInputsFilled = verificationCode.every(code => code !== '');\n\n const getUserdata = async code => {\n try {\n const { data } = await Api.get(`/auth/getinviteuser/${code}`);\n if (data.data.invitation.email) {\n // if not expired and correct link\n setUser({\n fname: data.data.invitation.firstName,\n lname: data.data.invitation.lastName,\n email: data.data.invitation.email,\n });\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n history.push('/login');\n }\n };\n\n const handleCodeChange = (idx, val) => {\n const code = [...verificationCode];\n code[idx] = val;\n setVerificationCode(code);\n };\n\n const handlePaste = pastedArray => {\n setVerificationCode(pastedArray);\n };\n\n const handleResendCode = async () => {\n try {\n await Api.post('/auth/resend-confirmation-code', {\n email: user?.email || registeringUserEmail,\n });\n enqueueSnackbar('Successfully sent!', { variant: 'success' });\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n }\n };\n\n const handleSubmit = async () => {\n try {\n setIsFetching(true);\n if (isForgotPassword) {\n const res = await Api.post('/auth/confirm-forgot-email', {\n email: user?.email || registeringUserEmail,\n code: verificationCode?.join(''),\n });\n if (res.status === 200) {\n history.push({ pathname: `/auth/reset/${res.data.data}` });\n }\n } else {\n const res = await Api.post('/auth/confirm-activation/', {\n email: user?.email || registeringUserEmail,\n code: verificationCode?.join(''),\n });\n await loginUser(res.data, history);\n }\n } catch (err) {\n if (err.response.data.data === TOKEN_RESPONSE_CODES.expiredConfirmationCode) {\n setIsCodeExpired(true);\n }\n setError(getError(err));\n }\n setIsFetching(false);\n };\n\n useEffect(() => {\n if (regCode) getUserdata(regCode);\n }, [regCode]);\n\n return (\n \n \n
\n
\n
\n \n
\n
\n
\n
{title} \n
{subTitle}
\n
\n
\n {error &&
{error}
}\n {isCodeExpired && (\n
{'Click \"Resend Code\" below to get the new one.'}
\n )}\n
\n \n Submit\n \n
\n
\n \n Resend Code\n \n
\n
\n
\n \n );\n};\n\nexport default connect(\n null,\n { loginUser },\n)(EmailVerification);\n","export default __webpack_public_path__ + \"static/media/presentation1.12f1b247.png\";","export default __webpack_public_path__ + \"static/media/presentation2.bad07ce6.png\";","export default __webpack_public_path__ + \"static/media/avatars_group.664254e4.png\";","import React from 'react';\nimport { MarketingBanner } from './components/marketingBanner';\nimport { MarketingCourses } from './components/MarketingCourses';\nimport { MarketingFooter } from './components/MarketingFooter';\nimport { MarketingHeader } from './components/MarketingHeader';\nimport { MarketingPresentationSection } from './components/MarketingPresentationSection';\nimport SendQuestionSection from 'shared/components/SendQuestionSection';\n\nexport const MarketingLanding = () => {\n return (\n \n \n \n \n \n \n \n
\n );\n};\n","/* eslint-disable consistent-return */\nimport React, { useRef, useEffect } from 'react';\n\nconst WithMouseFollowOrb = WrappedComponent => {\n return props => {\n return ;\n };\n};\n\nconst useMouseFollowOrb = () => {\n const canvasRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n const rect = canvas.getBoundingClientRect();\n canvas.width = rect.width;\n canvas.height = rect.height;\n\n let gradientX = 100;\n let gradientY = 150;\n\n const updateGradient = (x, y) => {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n let gradient = ctx.createRadialGradient(x, y, 30, x, y, 700);\n // Define gradient color stops\n gradient.addColorStop(0, 'rgba(110, 75, 155, 0.8)');\n gradient.addColorStop(0.2, 'rgba(110, 75, 155, 0.7)');\n gradient.addColorStop(0.3, 'rgba(110, 75, 155, 0.6)');\n gradient.addColorStop(0.4, 'rgba(110, 75, 155, 0.4)');\n gradient.addColorStop(0.5, 'rgba(110, 75, 155, 0.2)');\n gradient.addColorStop(1, 'rgba(110, 75, 155, 0.1)');\n\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n };\n\n updateGradient(gradientX, gradientY);\n\n const mouseMoveHandler = e => {\n gradientX = e.clientX - rect.left;\n gradientY = e.clientY - rect.top;\n updateGradient(gradientX, gradientY);\n };\n\n window.addEventListener('mousemove', mouseMoveHandler);\n\n return () => window.removeEventListener('mousemove', mouseMoveHandler);\n }, []);\n\n return canvasRef;\n};\n\nexport { WithMouseFollowOrb, useMouseFollowOrb };\n","import React from 'react';\nimport { useLocation } from 'react-router-dom';\n\nconst useQuery = () => {\n const { search } = useLocation();\n\n return React.useMemo(() => {\n const params = new URLSearchParams(search);\n const paramsObject = {};\n for (let param of params) {\n paramsObject[param[0]] = param[1];\n }\n return paramsObject;\n }, [search]);\n};\n\nexport default useQuery;\n","import { WithMouseFollowOrb, useMouseFollowOrb } from 'HOC/WithMouseFollowOrb';\nimport React, { useLayoutEffect, useRef, useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { useParams } from 'react-router-dom';\nimport useQuery from 'shared/hooks/useQuery';\n\nconst LeftLayout = props => {\n const { description, entityForwardPageKey = 'register' } = props;\n const { urlPath } = useParams();\n const { spec, entity } = useQuery();\n const canvasRef = useMouseFollowOrb();\n const [entityFontSize, setEntityFontSize] = useState(84);\n\n const charsCount = entity?.length || 1;\n\n useLayoutEffect(() => {\n const calculateFontSize = () => {\n if (entity) {\n const entityTitleElm = document.getElementById('entity_title');\n const entityTitleWidth = entityTitleElm.offsetWidth;\n const newFontSize = (entityTitleWidth / charsCount) * 1.8;\n setEntityFontSize(newFontSize);\n }\n };\n\n calculateFontSize();\n\n window.addEventListener('resize', calculateFontSize);\n\n return () => window.removeEventListener('resize', calculateFontSize);\n }, []);\n\n return (\n \n
\n \n
\n
\n {entity ? (\n <>\n
\n {`Not a ${entity} User? >`}\n
\n
\n
Welcome
\n
\n {entity}\n
\n
User
\n
\n {description &&
{description}
}\n >\n ) : (\n
\n
Welcome
\n
{description}
\n
\n )}\n
\n
\n );\n};\n\nexport default WithMouseFollowOrb(LeftLayout);\n","import { WithMouseFollowOrb, useMouseFollowOrb } from 'HOC/WithMouseFollowOrb';\nimport React from 'react';\nimport Button from 'shared/components/Button';\n\nconst Banner = props => {\n const { name, description, bannerVideo, bannerImg } = props;\n const canvasRef = useMouseFollowOrb();\n return (\n \n
\n
\n
\n
{name} \n
{description}
\n
See More \n
\n
\n
\n {bannerImg &&
}\n
\n
\n );\n};\n\nexport default WithMouseFollowOrb(Banner);\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport { WithMouseFollowOrb, useMouseFollowOrb } from 'HOC/WithMouseFollowOrb';\nimport React from 'react';\nimport { confirmAlert } from 'react-confirm-alert';\nimport { IconForward } from 'shared/components/Icons';\n\nconst LinkTitleSection = ({ externalLinks }) => {\n const canvasRef = useMouseFollowOrb();\n const filteredExternalLinks =\n !!externalLinks &&\n externalLinks.filter(link => link.title && link.description && link.productLink);\n if (!filteredExternalLinks?.length) return null;\n\n const goWithLink = (url, e) => {\n e.preventDefault();\n if (!url) return;\n confirmAlert({\n message:\n 'A new browser tab will now be opened. You can close it at any time and come back to this tab to continue.',\n buttons: [\n {\n label: 'Continue',\n onClick: () => window.open(url, '_blank'),\n },\n {\n label: 'Close',\n },\n ],\n });\n };\n\n return (\n \n );\n};\n\nexport default WithMouseFollowOrb(LinkTitleSection);\n","export default __webpack_public_path__ + \"static/media/16y.6f576432.png\";","export default __webpack_public_path__ + \"static/media/20y.edf8abf5.png\";","export default __webpack_public_path__ + \"static/media/30y.2a5423f8.png\";","export default __webpack_public_path__ + \"static/media/40y.69d49b97.png\";","export default __webpack_public_path__ + \"static/media/50y.7877fbf5.png\";","export default __webpack_public_path__ + \"static/media/60y.7d3b1d22.png\";","export default __webpack_public_path__ + \"static/media/preparation-card-img.5861a4a8.png\";","import React from 'react';\nimport Year16Img from '../mockImages/16y.png';\nimport Year20Img from '../mockImages/20y.png';\nimport Year30Img from '../mockImages/30y.png';\nimport Year40Img from '../mockImages/40y.png';\nimport Year50Img from '../mockImages/50y.png';\nimport Year60Img from '../mockImages/60y.png';\nimport CardImg from '../mockImages/preparation-card-img.png';\nimport Button from 'shared/components/Button';\n\nconst ageGroups = [\n { ageRange: '13-19', image: Year16Img },\n { ageRange: '20s', image: Year20Img },\n { ageRange: '30s', image: Year30Img },\n { ageRange: '40s', image: Year40Img },\n { ageRange: '50s', image: Year50Img },\n { ageRange: '60+', image: Year60Img },\n];\n\nconst boldTexts = [\n 'Evaluation and Appropriate Testing',\n 'STI Testing',\n 'Immunizations',\n 'Visual Exam of Breast & Pelvic Area',\n];\n\nconst ExamPreparationSection = () => {\n return (\n \n
\n
What to Expect at Your Well Woman Exam \n
\n Depending on your age, the Well Woman exam may cover different health topics. Let’s\n take a look at what you can expect.\n
\n
\n
\n {ageGroups.map((group, index) => (\n
\n
\n
\n
\n
{group.ageRange} \n
\n ))}\n
\n
\n
\n
In Your Late Teens \n
\n {' '}\n This is likely your first Well Woman exam. Now is the time to establish open\n communication with your health provider. Be willing to have an honest discussion about\n your sexual and reproductive health – no topic should be off-limits. While your Well\n Woman exam will be designed with your unique health in mind, you can expect it to\n include the following elements:\n
\n
\n {boldTexts.map((text, idx) => (\n \n {text}\n \n ))}\n
\n
Learn More \n
\n
\n
\n
\n
\n
\n );\n};\n\nexport default ExamPreparationSection;\n","import React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\nimport MuiExpansionPanel from '@material-ui/core/ExpansionPanel';\nimport MuiExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';\nimport MuiExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';\nimport Typography from '@material-ui/core/Typography';\nimport { CircleMinusIcon, CirclePlusIcon } from 'shared/components/Icons';\n\nconst ExpansionPanel = withStyles({\n root: {\n maxWidth: 768,\n borderBottom: '1px solid #EAECF0',\n boxShadow: 'none',\n '&:last-child': {\n borderBottom: 0,\n },\n '&:before': {\n display: 'none',\n },\n '&$expanded': {\n margin: 'auto',\n },\n },\n expanded: {},\n})(MuiExpansionPanel);\n\nconst ExpansionPanelSummary = withStyles({\n root: {\n padding: 0,\n borderBottom: '1px solid #EAECF0',\n marginBottom: -1,\n minHeight: 56,\n '& > p': {\n fontSize: '18px',\n fontWeight: 500,\n color: '#101828',\n },\n '&$expanded': {\n minHeight: 56,\n borderBottom: 0,\n },\n },\n content: {\n '&$expanded': {\n margin: '12px 0',\n },\n },\n expanded: {},\n})(MuiExpansionPanelSummary);\n\nconst ExpansionPanelDetails = withStyles(theme => ({\n root: {\n padding: '8px 48px 32px 0',\n '& > p': {\n fontSize: '16px',\n fontWeight: 400,\n color: '#667085',\n },\n '&$expanded': {\n borderBottom: '1px solid #EAECF0',\n },\n },\n}))(MuiExpansionPanelDetails);\n\nconst AccordionItem = ({ summary, details, expanded, onChange, panelKey }) => {\n return (\n \n : }>\n {summary} \n \n \n {details} \n \n \n );\n};\n\nexport default AccordionItem;\n","export default __webpack_public_path__ + \"static/media/hover_course_cover.61be8adc.png\";","export default __webpack_public_path__ + \"static/media/not-found.4c3aa28c.png\";","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React, { useEffect, useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { useSnackbar } from 'notistack';\nimport { LinearProgress } from '@material-ui/core';\nimport { COURSE_TYPES, DEFAULT_IMG_URLS } from 'configs/constants';\nimport Loading from 'shared/components/Loading';\nimport { getDate, getError } from 'utils/appHelpers';\nimport { SearchInput } from 'shared/components/SearchInput';\nimport ReactPaginate from 'react-paginate';\nimport { Api } from 'utils/connectors';\nimport CoursesProgressCircle from './CoursesProgressCircles';\nimport Button from 'shared/components/Button';\nimport FolderOpenImg from 'assets/product/folder-open.png';\nimport NotFoundImage from 'assets/product/not-found.png';\nimport Select from 'shared/components/Select';\n\nexport const SPECIALTIES = [\n { name: 'Surgery', value: 'Surgery' },\n { name: 'Radiology', value: 'Radiology' },\n { name: 'Fellowship in Radiology', value: 'Fellowship in Radiology' },\n { name: 'Oncology', value: 'Oncology' },\n { name: 'Radiation Oncology', value: 'Radiation Oncology' },\n { name: 'Medical Physics', value: 'Medical Physics' },\n { name: 'Internal Medicine', value: 'Internal Medicine' },\n { name: 'Dosimetry', value: 'Dosimetry' },\n { name: 'Other', value: 'Other' },\n { name: 'All Specialties', value: 'all' },\n];\n\nconst PRODUCT_COURSE_TAGS = [{ id: 1, name: 'Required' }, { id: 2, name: 'Not Required' }];\n\nconst getFirstLessonPath = course => {\n const firstLesson = course?.courseLessons?.[0]?.lesson;\n return `/courses/${course.id}/${COURSE_TYPES[(firstLesson?.contentType)]}/${firstLesson?.id}`;\n};\n\nconst NO_PAGE_DETAILS = {\n noPage: {\n title: This product doesn't have courses for your specialty ,\n description: (\n <>\n \n Please return later to check.\n
\n \n If you feel you appear here because of some problem then{' '}\n contact your Entity Manager. \n
\n >\n ),\n },\n noAccess: {\n title: You don’t have access to this Product page ,\n description: (\n <>\n \n For accessing this program you should be part of an Entity.\n
\n \n Ask your Organization Manager to add you as a{' '}\n member of the Entity before accessing this page.\n
\n >\n ),\n },\n};\n\nconst SpecialistCourses = props => {\n const { currentPage, userHasEntity, productId, userSpecialty } = props;\n const { enqueueSnackbar } = useSnackbar();\n const [search, setSearch] = useState('');\n const [activeSearchTags, setActiveSearchTags] = useState([]);\n const [pageCount, setPageCount] = useState(0);\n const [page, setPage] = useState(1);\n const [isFetching, setIsFetching] = useState(false);\n const [courses, setCourses] = useState(null);\n const [debouncedSearch, setDebouncedSearch] = useState(search);\n const [requiredCourses, setRequiredCourses] = useState([]);\n const [selectedSpecialtyForCourses, setSelectedSpecialtyForCourses] = useState(userSpecialty);\n\n const isUserSpecialtyChosen = selectedSpecialtyForCourses === userSpecialty;\n\n const specialtiesBesidesUsers = SPECIALTIES.filter(\n spec => spec?.name?.toLowerCase() !== userSpecialty?.toLowerCase(),\n );\n\n const noPage = !currentPage?.id;\n const noAccess = !userHasEntity;\n const noPageDetailsKey = noAccess ? 'noAccess' : 'noPage';\n const params = { page, search };\n const userCoursesForProgresses = requiredCourses ? requiredCourses.map(course => course) : [];\n\n const getCourses = async (tags, specialties) => {\n try {\n setIsFetching(true);\n const reqParams = { ...params };\n\n if (tags[0]) {\n reqParams.required = tags[0] === 1;\n }\n\n if (specialties?.length > 0) {\n reqParams.specialty = specialties.join(',');\n reqParams.all_users = specialties?.[0] !== userSpecialty;\n }\n\n const { data } = await Api.get(`/product/user-product/${productId}`, { params: reqParams });\n\n if (data?.data) {\n setCourses(data.data.courses.results || []);\n setPageCount(data.data.courses.pageCount);\n setPage(data.data.courses.currentPage);\n if (Array.isArray(data?.data?.required)) {\n setRequiredCourses(\n data.data.required.map(reqCourse => ({\n ...reqCourse,\n progress:\n data.data.courses.results.find(course => course.id === reqCourse.id)?.userCourse\n ?.progress || 0,\n })),\n );\n }\n }\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n setIsFetching(false);\n }\n };\n\n const handleGetUserSpecialtyCourses = () => {\n const all = specialtiesBesidesUsers[specialtiesBesidesUsers.length - 1];\n setSelectedSpecialtyForCourses([all]);\n setSelectedSpecialtyForCourses(userSpecialty);\n getCourses(activeSearchTags, [userSpecialty]);\n };\n\n const handleGetOtherSpecialtiesCourses = () => {\n const updatingSpecialties = specialtiesBesidesUsers.map(item => item.value).slice(0, -1);\n const all = specialtiesBesidesUsers[updatingSpecialties.length - 1];\n setSelectedSpecialtyForCourses(all);\n getCourses(activeSearchTags, updatingSpecialties);\n };\n\n const handleChangeSpecialty = ({ target }) => {\n let updatingSpecialties = [target.value];\n setSelectedSpecialtyForCourses(target.value);\n if (target.value === 'all' || !target.value) {\n updatingSpecialties = specialtiesBesidesUsers.map(item => item.value).slice(0, -1);\n }\n getCourses(activeSearchTags, updatingSpecialties);\n };\n\n const handleToggleSelectedTags = id => {\n if (activeSearchTags.includes(id)) {\n setActiveSearchTags([]);\n } else {\n setActiveSearchTags([id]);\n }\n };\n\n const handleSearch = e => setSearch(e.target.value);\n\n const handlePageClick = selectedItem => {\n setPage(selectedItem.selected + 1);\n };\n\n const handleNeedHelpClick = () => {\n const section = document.getElementById('question-submit-section');\n if (section) {\n section.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }\n };\n\n useEffect(() => {\n if (productId) getCourses(activeSearchTags, [selectedSpecialtyForCourses]);\n }, [productId, activeSearchTags, page, debouncedSearch, currentPage]);\n\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedSearch(search);\n }, 500);\n\n return () => {\n clearTimeout(handler);\n };\n }, [search]);\n\n return (\n <>\n {(noPage || noAccess) && (\n \n
\n {NO_PAGE_DETAILS[noPageDetailsKey].title}\n
\n {NO_PAGE_DETAILS[noPageDetailsKey].description}\n
\n Need Support?\n \n
\n
\n
\n
\n
\n )}\n <>\n \n
\n
Required Courses \n
\n Depending on your age, the Well Woman exam may cover different health topics. Let’s\n take a look at what you can expect.\n
\n
\n {isUserSpecialtyChosen && !isFetching && (\n
\n )}\n
\n
\n
\n
\n My Courses\n
\n
\n Other Specialties\n
\n
\n
\n
\n {PRODUCT_COURSE_TAGS.map((tag, idx) => (\n
handleToggleSelectedTags(tag.id)}\n className={`search-type-item ${activeSearchTags.includes(tag.id) &&\n 'active'}`}\n >\n {tag?.name}\n
\n ))}\n
\n
\n
getCourses(activeSearchTags)}\n onChange={handleSearch}\n value={search}\n placeholder='Search in materials'\n />\n \n
\n
\n {!courses || isFetching ? (\n
\n ) : courses.length ? (\n courses.map((course, index) => (\n
\n
\n
\n {course.userCourse?.progress !== undefined && isUserSpecialtyChosen && (\n
\n \n
\n )}\n
\n
\n
{course.title}
\n
{getDate(course.createdAt)}
\n
\n
{course.info}
\n \n ))\n ) : (\n
\n
\n
There is no courses to be shown
\n
\n Try checking other specialties or change the used filters\n
\n
\n )}\n
\n
\n
\n
\n >\n >\n );\n};\n\nexport default SpecialistCourses;\n","import React from 'react';\nimport SpecialistCourses from './SpecialistCourses';\n\nconst RequiredCoursesSection = props => {\n const { product, user } = props;\n const userHasEntity = user?.entities?.length;\n\n return ;\n};\n\nexport default RequiredCoursesSection;\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React, { useState } from 'react';\nimport { useSelector } from 'react-redux';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogActions from '@material-ui/core/DialogActions';\n\nimport { withStyles } from '@material-ui/core/styles';\nimport { getError } from 'utils/appHelpers';\n// Ensure you are using a compatible version of notistack with Material-UI v3.9\nimport { useSnackbar } from 'notistack';\nimport Button from 'shared/components/Button';\nimport ButtonLoading from 'shared/components/ButtonLoading';\n\nconst styles = theme => ({\n dialogTitle: {\n position: 'relative',\n },\n closeButton: {\n position: 'absolute',\n right: theme.spacing.unit,\n top: theme.spacing.unit,\n color: theme.palette.grey[500],\n },\n content: {\n padding: '16px 0',\n borderTop: '1px solid #D3D4D7',\n margin: '0 24px',\n height: 470,\n },\n footer: {\n boxShadow: '0px 0px 14px 0px #C7D2FF',\n borderTop: '1px solid #D3D4D7',\n margin: 0,\n height: 67,\n padding: '16px 24px',\n },\n applyBtn: {\n padding: '8px 55px',\n fontSize: 12,\n fontWeight: 400,\n },\n});\n\nconst EntitySelectDialog = ({\n isOpen,\n onClose,\n classes,\n assignProduct,\n productName,\n specialty,\n data,\n setUserSelectedEntity,\n}) => {\n const [isFetching, setIsFetching] = useState(false);\n const { enqueueSnackbar } = useSnackbar();\n\n const userEntities = useSelector(state => state.account.entities);\n const [selectedEntity, setSelectedEntity] = useState('');\n\n const addOrUpdateParam = (key, value) => {\n if (window.history.pushState) {\n const url = new URL(window.location.href);\n url.searchParams.set(key, value);\n window.history.pushState({ path: url.toString() }, '', url.toString());\n }\n };\n\n const handleApply = async () => {\n try {\n setIsFetching(true);\n await addOrUpdateParam('entity', selectedEntity);\n assignProduct(selectedEntity);\n setUserSelectedEntity(selectedEntity);\n } catch (err) {\n enqueueSnackbar(getError(err), { variant: 'error' });\n } finally {\n onClose();\n setIsFetching(false);\n }\n };\n\n return (\n <>\n \n \n Select the entity\n \n \n {data?.message ? (\n {data?.message}
\n ) : (\n \n Select one of you Entities from which you are assigned to take the course of \"\n {productName}\" as {specialty}.\n
\n )}\n \n {userEntities.map((entity, idx) => {\n const { name, imageUrl } = entity;\n return (\n
setSelectedEntity(name)}\n key={idx}\n className={`entity-item ${name === selectedEntity && 'active'}`}\n >\n {imageUrl ? (\n
\n
\n
\n ) : (\n
{name?.[0]}
\n )}\n
{name}
\n
\n );\n })}\n
\n \n \n \n {isFetching ? : 'Apply'}\n \n \n \n >\n );\n};\n\nexport default withStyles(styles)(EntitySelectDialog);\n","export default __webpack_public_path__ + \"static/media/service-and-maintenance.81d9d854.png\";","import React from 'react';\nimport ServiceAndMaintenanceImg from 'assets/service-and-maintenance.png';\n\nconst ServiceAndMaintenance = () => {\n return (\n \n
\n
Maintenance in Progress \n
Please come back soon \n
\n );\n};\n\nexport default ServiceAndMaintenance;\n","/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport React from 'react';\nimport Switch from 'react-switch';\n\nconst ToggleLabel = ({ text }) => (\n \n {text} \n
\n);\n\nconst Toggle = props => (\n {\n e.preventDefault();\n e.stopPropagation();\n return false;\n }}\n >\n \n }\n checkedIcon={ }\n height={16}\n width={30}\n onColor='#5aa83b'\n offColor='#fff'\n offHandleColor='#EFF4FB'\n onHandleColor='#eff4fb'\n />\n
\n);\n\nexport default Toggle;\n","import React, { useState } from 'react';\nimport { IconClose } from 'shared/components/Icons';\nimport Toggle from 'shared/components/Toggle';\n\nconst data = [\n {\n title: 'Necessary',\n content: (\n <>\n Necessary cookies are absolutely essential for the website to function properly. This\n category only includes cookies that ensures basic functionalities and security features of\n the website. These cookies do not store any personal information.\n >\n ),\n enabledText: 'Always Enabled',\n },\n {\n title: 'Unnecessary',\n name: 'unnecessary_state',\n content: (\n <>\n Any cookies that may not be particularly necessary for the website to function and is used\n specifically to collect user personal data via analytics, ads, other embedded contents are\n termed as non-necessary cookies. It is mandatory to procure user consent prior to running\n these cookies on your website.\n >\n ),\n },\n {\n title: 'Functional',\n name: 'functional_state',\n content: (\n <>\n Functional cookies help to perform certain functionalities like sharing the content of the\n website on social media platforms, collect feedbacks, and other third-party features.\n >\n ),\n },\n];\n\nconst CookieSettingsModal = ({ onClose, state, setState, saveAndAccept }) => {\n const [infoState, setInfoState] = useState(false);\n const [activeIndex, setActiveIndex] = useState(null);\n const toggleAccordion = index => setActiveIndex(activeIndex === index ? null : index);\n\n const handleCookieSettingsChange = (name, val) => {\n setState({ ...state, [name]: val });\n };\n\n return (\n <>\n \n
\n
Privacy Overview
\n
\n \n
\n
\n
\n
\n This website uses cookies to improve your experience while you navigate through the\n website. Out of these, the cookies that are categorized as necessary are stored on your\n browser as they are essential for the working of basic functionalities of the website.\n We also use third-party cookies that help us analyze and understand how you use this\n website. These cookies will be stored in your browser only with your consent. You also\n have the option to opt-out of these cookies. But opting out of some of these cookies may\n affect your browsing experience.\n
\n
setInfoState(!infoState)}\n >\n Show {infoState ? 'less' : 'more'} \n \n
\n {data.map((item, index) => {\n const isChecked = state[item.name];\n return (\n
\n toggleAccordion(index)}\n role='presentation'\n >\n \n
{item.title}
\n
\n {item.enabledText && item.enabledText}\n {!item.enabledText && (\n \n )}\n
\n
\n \n {activeIndex === index && {item.content}
}\n \n );\n })}\n
\n
\n
\n \n SAVE & ACCEPT\n \n
\n
\n >\n );\n};\n\nexport default CookieSettingsModal;\n","/* eslint-disable react/jsx-no-target-blank */\nimport React from 'react';\nimport { useState } from 'react';\nimport ReactModal from 'react-modal';\nimport { onOpenExternalUrl } from 'utils/appHelpers';\nimport { getFromStore, initCookieState, saveToStore } from 'utils/storeHelpers';\nimport CookieSettingsModal from './components/CookieSettingsModal';\n\nconst CookiePopup = () => {\n const storeState = getFromStore('cookie');\n const runState = storeState ? JSON.parse(storeState) : { ...initCookieState };\n const [open, toggleModal] = useState(false);\n const [state, setState] = useState(runState);\n\n const saveAndAccept = () => {\n const newState = { ...state, cookieAccepted: true };\n toggleModal(false);\n setState(newState);\n saveToStore('cookie', JSON.stringify(newState));\n };\n\n if (state.cookieAccepted) return null;\n\n return (\n <>\n \n
\n We use cookies on our website to give you the most relevant experience by remembering\n preferences and repeat visits. By clicking “Accept” you consent to store on your device\n all the technologies described in our{' '}\n
\n Cookie Policy\n \n . However, you may visit “Cookie Settings” to provide a more controlled consent.”\n
\n
\n toggleModal(true)}>\n Cookie settings\n \n \n ACCEPT\n \n
\n
\n \n toggleModal(false)}\n state={state}\n setState={setState}\n saveAndAccept={saveAndAccept}\n />\n \n >\n );\n};\n\nexport default CookiePopup;\n","import React, { useEffect, Fragment } from 'react';\nimport { Switch, BrowserRouter as Router } from 'react-router-dom';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { getAuthState } from './Auth/actions';\nimport { useSnackbar } from 'notistack';\n\n// Load Routes\nimport Main from './Main';\nimport Loading from 'shared/components/Loading';\nimport PublicViewer from './PublicViewer';\nimport Events from './Events';\nimport ComingSoon from './ComingSoon';\nimport PromoPage from './PromoPage';\nimport About from './Main/routes/About';\nimport PublicCaseView from './PublicCaseView';\nimport { WithTitleRoute, hasAccess } from 'utils/permissionHelper';\nimport PublicCourseView from './PublicCourseView';\nimport Login from './Auth/routes/Login';\nimport Forgot from './Auth/routes/Forgot';\nimport UserLicense from './Auth/routes/UserLicense';\nimport Reset from './Auth/routes/Reset';\nimport Register from './Auth/routes/Register';\nimport Verify from './Auth/routes/Verify';\nimport NoAccess from './Auth/routes/NoAccess';\nimport IncorrectLink from './Auth/routes/IncorrectLink';\nimport EmailVerification from './Auth/routes/EmailVerification';\nimport { MarketingLanding } from './Marketing';\nimport Product from './Product';\nimport ServiceAndMaintenance from './ServiceAndMaintenance';\nimport CookiePopup from 'shared/components/CookiePopup';\n\nconst IS_SERVICE_AND_MAINTENANCE = false;\n\nconst App = () => {\n const { enqueueSnackbar } = useSnackbar();\n const dispatch = useDispatch();\n const isAuth = useSelector(state => state.isAuthenticated);\n const user = useSelector(state => state.account);\n const isVisitorUser = hasAccess('visitor_user');\n\n useEffect(() => {\n if (isAuth === null) dispatch(getAuthState(enqueueSnackbar));\n //eslint-disable-next-line\n }, [getAuthState, isAuth, user]);\n\n useEffect(() => {\n if (window.location.hostname.startsWith('www.')) {\n const newUrl = window.location.href.replace('www.', '');\n window.location.replace(newUrl);\n }\n setInterval(() => {\n document.querySelector('iframe')?.remove();\n }, 1000);\n }, []);\n\n // if (isAuth === -2) return ;\n\n if (isAuth === -1) return ;\n\n return (\n \n \n \n {isAuth !== null && (\n \n \n \n \n \n \n {/* */}\n {/* */}\n \n {/* For the shared course view only on logout state */}\n {!isAuth && }\n {isVisitorUser && }\n \n {/* {isVisitorUser && } */}\n {(isVisitorUser || isAuth == null) && (\n \n )}\n {(isVisitorUser || isAuth == null) && (\n \n )}\n {(isVisitorUser || isAuth == null) && (\n \n )}\n {(isVisitorUser || isAuth == null) && (\n \n )}\n {(isVisitorUser || isAuth == null) && (\n \n )}\n {(isVisitorUser || isAuth == null) && (\n \n )}\n {(isVisitorUser || isAuth == null) && (\n \n )}\n {(isVisitorUser || isAuth == null) && (\n \n )}\n {(isVisitorUser || isAuth == null) && (\n \n )}\n \n \n )}\n {isAuth == null && }\n \n \n );\n};\n\nexport default App;\n","import 'react-app-polyfill/ie9';\nimport 'react-app-polyfill/ie11';\nimport 'react-app-polyfill/stable';\nimport React from 'react';\nimport { render } from 'react-dom';\nimport { Provider } from 'react-redux';\nimport { SnackbarProvider } from 'notistack';\nimport store from './redux/store';\nimport 'react-confirm-alert/src/react-confirm-alert.css';\nimport 'scss/app.scss';\nimport App from './app';\nimport ReactModal from 'react-modal';\n\nReactModal.setAppElement('#root');\nconst snackOpts = { vertical: 'top', horizontal: 'right' };\n\nrender(\n \n \n \n \n ,\n document.getElementById('root'),\n);\n"],"sourceRoot":""}