diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 9d312bc..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,26 +0,0 @@ -version: 2.1 -orbs: - node: circleci/node@1.1.6 -jobs: - build-and-test: - executor: - name: node/default - steps: - - checkout - - run: - name: Install Chrome headless dependencies - working_directory: / - command: | - sudo apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ - libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \ - libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ - libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ - ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget --fix-missing - - node/with-cache: - steps: - - run: npm install - - run: npm run test -workflows: - build-and-test: - jobs: - - build-and-test \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..e85c39b --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,17 @@ +module.exports = { + "env": { + "browser": true, + "es6": true + }, + "extends": "eslint:recommended", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 11, + "sourceType": "module" + }, + "rules": { + } +}; diff --git a/css/simulator.css b/css/simulator.css index 2ac1dde..114336c 100644 --- a/css/simulator.css +++ b/css/simulator.css @@ -2,4 +2,4 @@ * Copyright 2011-2019 The Bootstrap Authors * Copyright 2011-2019 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)*/:root{--blue: #007bff;--indigo: #6610f2;--purple: #6f42c1;--pink: #e83e8c;--red: #dc3545;--orange: #fd7e14;--yellow: #ffc107;--green: #28a745;--teal: #20c997;--cyan: #17a2b8;--white: #fff;--gray: #6c757d;--gray-dark: #343a40;--primary: #007bff;--secondary: #6c757d;--success: #28a745;--info: #17a2b8;--warning: #ffc107;--danger: #dc3545;--light: #f8f9fa;--dark: #343a40;--breakpoint-xs: 0;--breakpoint-sm: 576px;--breakpoint-md: 768px;--breakpoint-lg: 992px;--breakpoint-xl: 1200px;--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}*,*::before,*::after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-original-title]{text-decoration:underline;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):hover,a:not([href]):not([tabindex]):focus{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,liberation mono,courier new,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:2.5rem}h2,.h2{font-size:2rem}h3,.h3{font-size:1.75rem}h4,.h4{font-size:1.5rem}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}small,.small{font-size:80%;font-weight:400}mark,.mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media(min-width:576px){.container{max-width:540px}}@media(min-width:768px){.container{max-width:720px}}@media(min-width:992px){.container{max-width:960px}}@media(min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:flex;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col,.col-auto,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm,.col-sm-auto,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md,.col-md-auto,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg,.col-lg-auto,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{flex-basis:0;flex-grow:1;max-width:100%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-5{margin-left:41.6666666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333333333%}.offset-8{margin-left:66.6666666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333333333%}.offset-11{margin-left:91.6666666667%}@media(min-width:576px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-sm-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-sm-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-sm-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-sm-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.3333333333%}.offset-sm-2{margin-left:16.6666666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333333333%}.offset-sm-5{margin-left:41.6666666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333333333%}.offset-sm-8{margin-left:66.6666666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333333333%}.offset-sm-11{margin-left:91.6666666667%}}@media(min-width:768px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-md-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-md-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-md-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-md-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.3333333333%}.offset-md-2{margin-left:16.6666666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333333333%}.offset-md-5{margin-left:41.6666666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333333333%}.offset-md-8{margin-left:66.6666666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333333333%}.offset-md-11{margin-left:91.6666666667%}}@media(min-width:992px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-lg-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-lg-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-lg-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-lg-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.3333333333%}.offset-lg-2{margin-left:16.6666666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333333333%}.offset-lg-5{margin-left:41.6666666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333333333%}.offset-lg-8{margin-left:66.6666666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333333333%}.offset-lg-11{margin-left:91.6666666667%}}@media(min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-xl-2{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-xl-5{flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-xl-8{flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-xl-11{flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.3333333333%}.offset-xl-2{margin-left:16.6666666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333333333%}.offset-xl-5{margin-left:41.6666666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333333333%}.offset-xl-8{margin-left:66.6666666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333333333%}.offset-xl-11{margin-left:91.6666666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table th,.table td{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm th,.table-sm td{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered th,.table-bordered td{border:1px solid #dee2e6}.table-bordered thead th,.table-bordered thead td{border-bottom-width:2px}.table-borderless th,.table-borderless td,.table-borderless thead th,.table-borderless tbody+tbody{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>th,.table-primary>td{background-color:#b8daff}.table-primary th,.table-primary td,.table-primary thead th,.table-primary tbody+tbody{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>th,.table-secondary>td{background-color:#d6d8db}.table-secondary th,.table-secondary td,.table-secondary thead th,.table-secondary tbody+tbody{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>th,.table-success>td{background-color:#c3e6cb}.table-success th,.table-success td,.table-success thead th,.table-success tbody+tbody{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>th,.table-info>td{background-color:#bee5eb}.table-info th,.table-info td,.table-info thead th,.table-info tbody+tbody{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>th,.table-warning>td{background-color:#ffeeba}.table-warning th,.table-warning td,.table-warning thead th,.table-warning tbody+tbody{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>th,.table-danger>td{background-color:#f5c6cb}.table-danger th,.table-danger td,.table-danger thead th,.table-danger tbody+tbody{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>th,.table-light>td{background-color:#fdfdfe}.table-light th,.table-light td,.table-light thead th,.table-light tbody+tbody{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>th,.table-dark>td{background-color:#c6c8ca}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>th,.table-active>td{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark th,.table-dark td,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media(max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media(max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media(max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media(max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + 0.75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + 0.5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[size],select.form-control[multiple]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.was-validated .form-control:valid,.form-control.is-valid{border-color:#28a745;padding-right:calc(1.5em + 0.75rem);background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCA4IDgnPjxwYXRoIGZpbGw9JyMyOGE3NDUnIGQ9J00yLjMgNi43M0wuNiA0LjUzYy0uNC0xLjA0LjQ2LTEuNCAxLjEtLjhsMS4xIDEuNCAzLjQtMy44Yy42LS42MyAxLjYtLjI3IDEuMi43bC00IDQuNmMtLjQzLjUtLjguNC0xLjEuMXonLz48L3N2Zz4=);background-repeat:no-repeat;background-position:center right calc(0.375em + 0.1875rem);background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip,.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .custom-select:valid,.custom-select.is-valid{border-color:#28a745;padding-right:calc((1em + 0.75rem) * 3/4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .custom-select:valid:focus,.custom-select.is-valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip,.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip{display:block}.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip,.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip{display:block}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#28a745}.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip,.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip{display:block}.was-validated .custom-control-input:valid~.custom-control-label,.custom-control-input.is-valid~.custom-control-label{color:#28a745}.was-validated .custom-control-input:valid~.custom-control-label::before,.custom-control-input.is-valid~.custom-control-label::before{border-color:#28a745}.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip,.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip{display:block}.was-validated .custom-control-input:valid:checked~.custom-control-label::before,.custom-control-input.is-valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.was-validated .custom-control-input:valid:focus~.custom-control-label::before,.custom-control-input.is-valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before,.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.was-validated .custom-file-input:valid~.custom-file-label,.custom-file-input.is-valid~.custom-file-label{border-color:#28a745}.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip,.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip{display:block}.was-validated .custom-file-input:valid:focus~.custom-file-label,.custom-file-input.is-valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#dc3545;padding-right:calc(1.5em + 0.75rem);background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIGZpbGw9JyNkYzM1NDUnIHZpZXdCb3g9Jy0yIC0yIDcgNyc+PHBhdGggc3Ryb2tlPScjZGMzNTQ1JyBkPSdNMCAwbDMgM20wLTNMMCAzJy8+PGNpcmNsZSByPScuNScvPjxjaXJjbGUgY3g9JzMnIHI9Jy41Jy8+PGNpcmNsZSBjeT0nMycgcj0nLjUnLz48Y2lyY2xlIGN4PSczJyBjeT0nMycgcj0nLjUnLz48L3N2Zz4=);background-repeat:no-repeat;background-position:center right calc(0.375em + 0.1875rem);background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip,.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .custom-select:invalid,.custom-select.is-invalid{border-color:#dc3545;padding-right:calc((1em + 0.75rem) * 3/4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .custom-select:invalid:focus,.custom-select.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip,.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip,.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip{display:block}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#dc3545}.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip,.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip{display:block}.was-validated .custom-control-input:invalid~.custom-control-label,.custom-control-input.is-invalid~.custom-control-label{color:#dc3545}.was-validated .custom-control-input:invalid~.custom-control-label::before,.custom-control-input.is-invalid~.custom-control-label::before{border-color:#dc3545}.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip,.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip{display:block}.was-validated .custom-control-input:invalid:checked~.custom-control-label::before,.custom-control-input.is-invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.was-validated .custom-control-input:invalid:focus~.custom-control-label::before,.custom-control-input.is-invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before,.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.was-validated .custom-file-input:invalid~.custom-file-label,.custom-file-input.is-invalid~.custom-file-label{border-color:#dc3545}.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip,.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip{display:block}.was-validated .custom-file-input:invalid:focus~.custom-file-label,.custom-file-input.is-invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media(min-width:576px){.form-inline label{display:flex;align-items:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:flex;flex:0 0 auto;flex-flow:row wrap;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group,.form-inline .custom-select{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn:focus,.btn.focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary:focus,.btn-primary.focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled):active,.btn-primary:not(:disabled):not(.disabled).active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled):active:focus,.btn-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary:focus,.btn-secondary.focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled):active,.btn-secondary:not(:disabled):not(.disabled).active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled):active:focus,.btn-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success:focus,.btn-success.focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled):active,.btn-success:not(:disabled):not(.disabled).active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled):active:focus,.btn-success:not(:disabled):not(.disabled).active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info:focus,.btn-info.focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled):active,.btn-info:not(:disabled):not(.disabled).active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled):active:focus,.btn-info:not(:disabled):not(.disabled).active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning:focus,.btn-warning.focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled):active,.btn-warning:not(:disabled):not(.disabled).active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled):active:focus,.btn-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger:focus,.btn-danger.focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled):active,.btn-danger:not(:disabled):not(.disabled).active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled):active:focus,.btn-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light:focus,.btn-light.focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled):active,.btn-light:not(:disabled):not(.disabled).active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled):active:focus,.btn-light:not(:disabled):not(.disabled).active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark:focus,.btn-dark.focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled):active,.btn-dark:not(:disabled):not(.disabled).active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled):active:focus,.btn-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:focus,.btn-outline-primary.focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled):active,.btn-outline-primary:not(:disabled):not(.disabled).active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:focus,.btn-outline-secondary.focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled):active,.btn-outline-secondary:not(:disabled):not(.disabled).active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:focus,.btn-outline-success.focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled):active,.btn-outline-success:not(:disabled):not(.disabled).active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled):active:focus,.btn-outline-success:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:focus,.btn-outline-info.focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled):active,.btn-outline-info:not(:disabled):not(.disabled).active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled):active:focus,.btn-outline-info:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:focus,.btn-outline-warning.focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled):active,.btn-outline-warning:not(:disabled):not(.disabled).active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:focus,.btn-outline-danger.focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled):active,.btn-outline-danger:not(:disabled):not(.disabled).active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:focus,.btn-outline-light.focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled):active,.btn-outline-light:not(:disabled):not(.disabled).active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled):active:focus,.btn-outline-light:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:focus,.btn-outline-dark.focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled):active,.btn-outline-dark:not(:disabled):not(.disabled).active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link:focus,.btn-link.focus{text-decoration:underline;box-shadow:none}.btn-link:disabled,.btn-link.disabled{color:#6c757d;pointer-events:none}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media(prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropup,.dropright,.dropdown,.dropleft{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media(min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media(min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media(min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media(min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=top],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:hover,.dropdown-item:focus{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover{z-index:1}.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-control-plaintext,.input-group>.custom-select,.input-group>.custom-file{position:relative;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.form-control+.form-control,.input-group>.form-control+.custom-select,.input-group>.form-control+.custom-file,.input-group>.form-control-plaintext+.form-control,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.custom-file,.input-group>.custom-select+.form-control,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.custom-file,.input-group>.custom-file+.form-control,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.custom-file{margin-left:-1px}.input-group>.form-control:focus,.input-group>.custom-select:focus,.input-group>.custom-file .custom-file-input:focus~.custom-file-label{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.form-control:not(:last-child),.input-group>.custom-select:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.form-control:not(:first-child),.input-group>.custom-select:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-prepend,.input-group-append{display:flex}.input-group-prepend .btn,.input-group-append .btn{position:relative;z-index:2}.input-group-prepend .btn:focus,.input-group-append .btn:focus{z-index:3}.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.input-group-text,.input-group-append .input-group-text+.btn{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=radio],.input-group-text input[type=checkbox]{margin-top:0}.input-group-lg>.form-control:not(textarea),.input-group-lg>.custom-select{height:calc(1.5em + 1rem + 2px)}.input-group-lg>.form-control,.input-group-lg>.custom-select,.input-group-lg>.input-group-prepend>.input-group-text,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-append>.btn{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.form-control:not(textarea),.input-group-sm>.custom-select{height:calc(1.5em + 0.5rem + 2px)}.input-group-sm>.form-control,.input-group-sm>.custom-select,.input-group-sm>.input-group-prepend>.input-group-text,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-append>.btn{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text,.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCA4IDgnPjxwYXRoIGZpbGw9JyNmZmYnIGQ9J002LjU2NC43NWwtMy41OSAzLjYxMi0xLjUzOC0xLjU1TDAgNC4yNiAyLjk3NCA3LjI1IDggMi4xOTN6Jy8+PC9zdmc+)}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCA0IDQnPjxwYXRoIHN0cm9rZT0nI2ZmZicgZD0nTTAgMmg0Jy8+PC9zdmc+)}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9Jy00IC00IDggOCc+PGNpcmNsZSByPSczJyBmaWxsPScjZmZmJy8+PC9zdmc+)}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(0.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;transform:translateX(0.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + 0.75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + 0.5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + 0.75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + 0.75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + 0.75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + 0.75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:calc(1rem + 0.4rem);padding:0;background-color:transparent;appearance:none}.custom-range:focus{outline:none}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media(prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media(prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media(prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:hover,.nav-link:focus{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:hover,.navbar-toggler:focus{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media(max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media(min-width:576px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media(max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media(min-width:768px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media(max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media(min-width:992px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media(max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media(min-width:1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .nav-link.active{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PScwIDAgMzAgMzAnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggc3Ryb2tlPSdyZ2JhKDAsIDAsIDAsIDAuNSknIHN0cm9rZS13aWR0aD0nMicgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbWl0ZXJsaW1pdD0nMTAnIGQ9J000IDdoMjJNNCAxNWgyMk00IDIzaDIyJy8+PC9zdmc+)}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .nav-link.active{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PScwIDAgMzAgMzAnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggc3Ryb2tlPSdyZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSknIHN0cm9rZS13aWR0aD0nMicgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbWl0ZXJsaW1pdD0nMTAnIGQ9J000IDdoMjJNNCAxNWgyMk00IDIzaDIyJy8+PC9zdmc+)}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(0.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card-deck{display:flex;flex-direction:column}.card-deck .card{margin-bottom:15px}@media(min-width:576px){.card-deck{flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:flex;flex:1 0 0%;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:flex;flex-direction:column}.card-group>.card{margin-bottom:15px}@media(min-width:576px){.card-group{flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media(min-width:576px){.card-columns{column-count:3;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:flex;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:hover,a.badge:focus{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:hover,a.badge-primary:focus{color:#fff;background-color:#0062cc}a.badge-primary:focus,a.badge-primary.focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:hover,a.badge-secondary:focus{color:#fff;background-color:#545b62}a.badge-secondary:focus,a.badge-secondary.focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:hover,a.badge-success:focus{color:#fff;background-color:#1e7e34}a.badge-success:focus,a.badge-success.focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:hover,a.badge-info:focus{color:#fff;background-color:#117a8b}a.badge-info:focus,a.badge-info.focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:hover,a.badge-warning:focus{color:#212529;background-color:#d39e00}a.badge-warning:focus,a.badge-warning.focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:hover,a.badge-danger:focus{color:#fff;background-color:#bd2130}a.badge-danger:focus,a.badge-danger.focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:hover,a.badge-light:focus{color:#212529;background-color:#dae0e5}a.badge-light:focus,a.badge-light.focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:hover,a.badge-dark:focus{color:#fff;background-color:#1d2124}a.badge-dark:focus,a.badge-dark.focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media(min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media(prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media(prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-horizontal{flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}@media(min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media(min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media(min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media(min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):hover,.close:not(:disabled):not(.disabled):focus{opacity:.75}button.close{padding:0;background-color:transparent;border:0;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media(prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-header,.modal-dialog-scrollable .modal-footer{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;align-items:center;justify-content:flex-end;padding:1rem;border-top:1px solid #dee2e6;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media(min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[x-placement^=top]{padding:.4rem 0}.bs-tooltip-top .arrow,.bs-tooltip-auto[x-placement^=top] .arrow{bottom:0}.bs-tooltip-top .arrow::before,.bs-tooltip-auto[x-placement^=top] .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-right,.bs-tooltip-auto[x-placement^=right]{padding:0 .4rem}.bs-tooltip-right .arrow,.bs-tooltip-auto[x-placement^=right] .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-right .arrow::before,.bs-tooltip-auto[x-placement^=right] .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[x-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .arrow,.bs-tooltip-auto[x-placement^=bottom] .arrow{top:0}.bs-tooltip-bottom .arrow::before,.bs-tooltip-auto[x-placement^=bottom] .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-left,.bs-tooltip-auto[x-placement^=left]{padding:0 .4rem}.bs-tooltip-left .arrow,.bs-tooltip-auto[x-placement^=left] .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-left .arrow::before,.bs-tooltip-auto[x-placement^=left] .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::before,.popover .arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-top,.bs-popover-auto[x-placement^=top]{margin-bottom:.5rem}.bs-popover-top>.arrow,.bs-popover-auto[x-placement^=top]>.arrow{bottom:calc((0.5rem + 1px) * -1)}.bs-popover-top>.arrow::before,.bs-popover-auto[x-placement^=top]>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.arrow::after,.bs-popover-auto[x-placement^=top]>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-right,.bs-popover-auto[x-placement^=right]{margin-left:.5rem}.bs-popover-right>.arrow,.bs-popover-auto[x-placement^=right]>.arrow{left:calc((0.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-right>.arrow::before,.bs-popover-auto[x-placement^=right]>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-right>.arrow::after,.bs-popover-auto[x-placement^=right]>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom,.bs-popover-auto[x-placement^=bottom]{margin-top:.5rem}.bs-popover-bottom>.arrow,.bs-popover-auto[x-placement^=bottom]>.arrow{top:calc((0.5rem + 1px) * -1)}.bs-popover-bottom>.arrow::before,.bs-popover-auto[x-placement^=bottom]>.arrow::before{top:0;border-width:0 .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.arrow::after,.bs-popover-auto[x-placement^=bottom]>.arrow::after{top:1px;border-width:0 .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[x-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-left,.bs-popover-auto[x-placement^=left]{margin-right:.5rem}.bs-popover-left>.arrow,.bs-popover-auto[x-placement^=left]>.arrow{right:calc((0.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-left>.arrow::before,.bs-popover-auto[x-placement^=left]>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-left>.arrow::after,.bs-popover-auto[x-placement^=left]>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-left),.active.carousel-item-right{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-right),.active.carousel-item-left{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media(prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion:reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIGZpbGw9JyNmZmYnIHZpZXdCb3g9JzAgMCA4IDgnPjxwYXRoIGQ9J001LjI1IDBsLTQgNCA0IDQgMS41LTEuNS0yLjUtMi41IDIuNS0yLjUtMS41LTEuNXonLz48L3N2Zz4=)}.carousel-control-next-icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIGZpbGw9JyNmZmYnIHZpZXdCb3g9JzAgMCA4IDgnPjxwYXRoIGQ9J00yLjc1IDBsLTEuNSAxLjUgMi41IDIuNS0yLjUgMi41IDEuNSAxLjUgNC00LTQtNHonLz48L3N2Zz4=)}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:hover,a.bg-primary:focus,button.bg-primary:hover,button.bg-primary:focus{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:hover,a.bg-secondary:focus,button.bg-secondary:hover,button.bg-secondary:focus{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:hover,a.bg-success:focus,button.bg-success:hover,button.bg-success:focus{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:hover,a.bg-info:focus,button.bg-info:hover,button.bg-info:focus{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:hover,a.bg-warning:focus,button.bg-warning:hover,button.bg-warning:focus{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:hover,a.bg-danger:focus,button.bg-danger:hover,button.bg-danger:focus{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:hover,a.bg-light:focus,button.bg-light:hover,button.bg-light:focus{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:hover,a.bg-dark:focus,button.bg-dark:hover,button.bg-dark:focus{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media(min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media(min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media(min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media(min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.8571428571%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media(min-width:576px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media(min-width:768px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media(min-width:992px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media(min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media(min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media(min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media(min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media(min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports(position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:transparent}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media(min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media(min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media(min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media(min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,liberation mono,courier new,monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media(min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media(min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media(min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media(min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:hover,a.text-primary:focus{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:hover,a.text-secondary:focus{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:hover,a.text-success:focus{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:hover,a.text-info:focus{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:hover,a.text-warning:focus{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:hover,a.text-danger:focus{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:hover,a.text-light:focus{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:hover,a.text-dark:focus{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,*::before,*::after{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}pre,blockquote{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered th,.table-bordered td{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}}/*!* Font Awesome Free 5.12.0 by @fontawesome - https://fontawesome.com -* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)*/@font-face{font-family:'font awesome 5 free';font-style:normal;font-weight:900;font-display:auto;src:url(/assets/font-awesome/fa-solid-900-f29eef66eb3ad0e3574d8edb4b9e72a04692a0b2b92d99eb7c4b977611ddd4fe.eot);src:url(/assets/font-awesome/fa-solid-900-f29eef66eb3ad0e3574d8edb4b9e72a04692a0b2b92d99eb7c4b977611ddd4fe.eot#iefix) format("embedded-opentype"),url(/assets/font-awesome/fa-solid-900-787d76ad6deab67ccf8bac1b584260205e114f508fc5542b612e3f75d49a34e4.woff2) format("woff2"),url(/assets/font-awesome/fa-solid-900-3b60c77e0c81c1c9cdc9adb96ade6dbac7ef2b9402a316185855de7122e517db.woff) format("woff"),url(/assets/font-awesome/fa-solid-900-0389b061db08d406704c9bb8819e09c3558ac956287b3e9da8e6645a79d528ea.ttf) format("truetype"),url(/assets/font-awesome/fa-solid-900-eaf0f3d0cadad17eed1045cf68c30db7abffb4c8ef4ee9cb5f4026fdefc99a59.svg#fontawesome) format("svg")}@font-face{font-family:'font awesome 5 free';font-style:normal;font-weight:400;font-display:auto;src:url(/assets/font-awesome/fa-regular-400-c1465a6b8743622f759b08a6d5336e57eb6eabdba1b6393fba6d30d45382f3d2.eot);src:url(/assets/font-awesome/fa-regular-400-c1465a6b8743622f759b08a6d5336e57eb6eabdba1b6393fba6d30d45382f3d2.eot#iefix) format("embedded-opentype"),url(/assets/font-awesome/fa-regular-400-86e496b536b26ba60cdb68df9dd9143b19a63b65e30e373b0321833aab1295d6.woff2) format("woff2"),url(/assets/font-awesome/fa-regular-400-864c8f702a5e63198bb76ef0240b599cc065d4904c2afc5da6c8a29bbf0d6c64.woff) format("woff"),url(/assets/font-awesome/fa-regular-400-6a335d1ce152f2b5b02bf82cc445b02d1abccd7f408a87113424b5f8fcbbfade.ttf) format("truetype"),url(/assets/font-awesome/fa-regular-400-7ab8a39f4c48b743bd6acbbf5ea4c7547efcb62ad966e9bef3e41996afc44fd4.svg#fontawesome) format("svg")}@font-face{font-family:'font awesome 5 brands';font-style:normal;font-weight:400;font-display:auto;src:url(/assets/font-awesome/fa-brands-400-e9fdf947c39f06f1b5e63c58eea2f2f74850421b4e32047dacb9c7b75dd42a16.eot);src:url(/assets/font-awesome/fa-brands-400-e9fdf947c39f06f1b5e63c58eea2f2f74850421b4e32047dacb9c7b75dd42a16.eot#iefix) format("embedded-opentype"),url(/assets/font-awesome/fa-brands-400-8e4560c16c7970efa47680450b2cf239d4a482c056d308acea12bb9022906c8b.woff2) format("woff2"),url(/assets/font-awesome/fa-brands-400-1af816db9a686faa76bfbda779df959e6213de714a94b87aa7374a151f6f4900.woff) format("woff"),url(/assets/font-awesome/fa-brands-400-6b347ff01e588a2a6909ccd7f7f5866b27484391547e9df451ab9b4c27920c71.ttf) format("truetype"),url(/assets/font-awesome/fa-brands-400-36b51fbe6b87587541b9173cd79c16c6b359ad22a172b922204f9aa87411acd9.svg#fontawesome) format("svg")}.fa,.fas{font-family:'font awesome 5 free';font-weight:900}.far{font-family:'font awesome 5 free';font-weight:400}.fab{font-family:'font awesome 5 brands'}.fa,.fas,.far,.fal,.fad,.fab{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.3333333333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fas.fa-pull-left,.far.fa-pull-left,.fal.fa-pull-left,.fab.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fas.fa-pull-right,.far.fa-pull-right,.fal.fa-pull-right,.fab.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scale(-1,1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";transform:scale(1,-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";transform:scale(-1,-1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-flip-both{filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\f907"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\f913"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\f91a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\f91e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\f941"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\f949"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.moduleProperty input{color:#fff}.side{width:330px;max-width:500px;min-width:200px;overflow-x:hidden;overflow-y:scroll}.report-sidebar a{border-radius:30px 5px 5px 30px;color:#fff;font-size:13px;padding:15px;position:absolute;right:-111px;text-decoration:none;top:90%;transition:.3s;width:150px;z-index:999}.report-sidebar a:hover{right:0} \ No newline at end of file +* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)*/@font-face{font-family:'font awesome 5 free';font-style:normal;font-weight:900;font-display:auto;src:url(/assets/font-awesome/fa-solid-900-f29eef66eb3ad0e3574d8edb4b9e72a04692a0b2b92d99eb7c4b977611ddd4fe.eot);src:url(/assets/font-awesome/fa-solid-900-f29eef66eb3ad0e3574d8edb4b9e72a04692a0b2b92d99eb7c4b977611ddd4fe.eot#iefix) format("embedded-opentype"),url(/assets/font-awesome/fa-solid-900-787d76ad6deab67ccf8bac1b584260205e114f508fc5542b612e3f75d49a34e4.woff2) format("woff2"),url(/assets/font-awesome/fa-solid-900-3b60c77e0c81c1c9cdc9adb96ade6dbac7ef2b9402a316185855de7122e517db.woff) format("woff"),url(/assets/font-awesome/fa-solid-900-0389b061db08d406704c9bb8819e09c3558ac956287b3e9da8e6645a79d528ea.ttf) format("truetype"),url(/assets/font-awesome/fa-solid-900-eaf0f3d0cadad17eed1045cf68c30db7abffb4c8ef4ee9cb5f4026fdefc99a59.svg#fontawesome) format("svg")}@font-face{font-family:'font awesome 5 free';font-style:normal;font-weight:400;font-display:auto;src:url(/assets/font-awesome/fa-regular-400-c1465a6b8743622f759b08a6d5336e57eb6eabdba1b6393fba6d30d45382f3d2.eot);src:url(/assets/font-awesome/fa-regular-400-c1465a6b8743622f759b08a6d5336e57eb6eabdba1b6393fba6d30d45382f3d2.eot#iefix) format("embedded-opentype"),url(/assets/font-awesome/fa-regular-400-86e496b536b26ba60cdb68df9dd9143b19a63b65e30e373b0321833aab1295d6.woff2) format("woff2"),url(/assets/font-awesome/fa-regular-400-864c8f702a5e63198bb76ef0240b599cc065d4904c2afc5da6c8a29bbf0d6c64.woff) format("woff"),url(/assets/font-awesome/fa-regular-400-6a335d1ce152f2b5b02bf82cc445b02d1abccd7f408a87113424b5f8fcbbfade.ttf) format("truetype"),url(/assets/font-awesome/fa-regular-400-7ab8a39f4c48b743bd6acbbf5ea4c7547efcb62ad966e9bef3e41996afc44fd4.svg#fontawesome) format("svg")}@font-face{font-family:'font awesome 5 brands';font-style:normal;font-weight:400;font-display:auto;src:url(/assets/font-awesome/fa-brands-400-e9fdf947c39f06f1b5e63c58eea2f2f74850421b4e32047dacb9c7b75dd42a16.eot);src:url(/assets/font-awesome/fa-brands-400-e9fdf947c39f06f1b5e63c58eea2f2f74850421b4e32047dacb9c7b75dd42a16.eot#iefix) format("embedded-opentype"),url(/assets/font-awesome/fa-brands-400-8e4560c16c7970efa47680450b2cf239d4a482c056d308acea12bb9022906c8b.woff2) format("woff2"),url(/assets/font-awesome/fa-brands-400-1af816db9a686faa76bfbda779df959e6213de714a94b87aa7374a151f6f4900.woff) format("woff"),url(/assets/font-awesome/fa-brands-400-6b347ff01e588a2a6909ccd7f7f5866b27484391547e9df451ab9b4c27920c71.ttf) format("truetype"),url(/assets/font-awesome/fa-brands-400-36b51fbe6b87587541b9173cd79c16c6b359ad22a172b922204f9aa87411acd9.svg#fontawesome) format("svg")}.fa,.fas{font-family:'font awesome 5 free';font-weight:900}.far{font-family:'font awesome 5 free';font-weight:400}.fab{font-family:'font awesome 5 brands'}.fa,.fas,.far,.fal,.fad,.fab{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.3333333333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fas.fa-pull-left,.far.fa-pull-left,.fal.fa-pull-left,.fab.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fas.fa-pull-right,.far.fa-pull-right,.fal.fa-pull-right,.fab.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scale(-1,1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";transform:scale(1,-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";transform:scale(-1,-1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-flip-both{filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\f907"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\f913"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\f91a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\f91e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\f941"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\f949"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.subcircuitdialog{display:none;overflow-x:hidden;overflow-y:auto}.moduleProperty input{color:#fff}.side{width:330px;max-width:500px;min-width:200px;overflow-x:hidden;overflow-y:scroll}.report-sidebar a{border-radius:30px 5px 5px 30px;color:#fff;font-size:13px;padding:15px;position:absolute;right:-111px;text-decoration:none;top:90%;transition:.3s;width:150px;z-index:999}.report-sidebar a:hover{right:0} \ No newline at end of file diff --git a/index.html b/index.html index c99cecb..d8b7e5f 100644 --- a/index.html +++ b/index.html @@ -1,296 +1,609 @@ - - + + + - - - - +
+
-
- +
+ +
+
+
+
+
+ Restricted elements used: + +
-
-
-
-
-
Restricted elements used:
-
-
-
-
- - -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
-
- -
-
- -
- +
+
+ + +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
- - - +
+ +
+
+
- - - - - - -
\ No newline at end of file + + + + + + + + + +
+ diff --git a/jest.config.js b/jest.config.js index 48bbbbb..737d7ca 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,6 @@ module.exports = { preset: "jest-puppeteer", + setupFiles: ["./jest.setup.js"], globals: { URL: "http://localhost:8080" }, diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..6dd419b --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,21 @@ +/** + * @jest-environment jsdom + */ + +global.window = window +window.Jquery = require('jquery'); +window.$ = require('jquery'); +global.jQuery = require('jquery'); + +window.restrictedElements = [] +window.userSignedIn = true; +window.embed = false; + +import Array from './src/arrayHelpers' +window["Array"] = Array +require('jquery-ui-bundle'); + +const fs = require('fs'); +const path = require('path'); +const html = fs.readFileSync(path.resolve(__dirname, './index.html'), 'utf8'); +document.documentElement.innerHTML = html.toString(); diff --git a/package-lock.json b/package-lock.json index 416f904..a2ff8a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2103,6 +2103,12 @@ "acorn-walk": "^6.0.1" } }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, "acorn-walk": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", @@ -3285,6 +3291,12 @@ } } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "chokidar": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", @@ -3358,6 +3370,21 @@ } } }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -4012,6 +4039,15 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -4197,6 +4233,258 @@ } } }, + "eslint": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.2.0.tgz", + "integrity": "sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.0.0", + "eslint-visitor-keys": "^1.2.0", + "espree": "^7.1.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint-scope": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -4207,12 +4495,63 @@ "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", + "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", + "dev": true + }, + "espree": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", + "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", + "dev": true, + "requires": { + "acorn": "^7.2.0", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.2.0" + }, + "dependencies": { + "acorn": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", + "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", + "dev": true + } + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } + } + }, "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", @@ -4462,6 +4801,17 @@ } } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -4592,6 +4942,24 @@ "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", "dev": true }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, "file-loader": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", @@ -4790,6 +5158,34 @@ "resolve-dir": "^1.0.1" } }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -4826,6 +5222,12 @@ } } }, + "font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -5483,6 +5885,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -5986,6 +6394,12 @@ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "ignore-walk": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", @@ -5994,6 +6408,24 @@ "minimatch": "^3.0.4" } }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -6040,6 +6472,117 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, + "inquirer": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", + "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "internal-ip": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", @@ -8465,6 +9008,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -8941,6 +9490,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", @@ -9488,6 +10043,15 @@ "readable-stream": "^2.1.5" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-asn1": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", @@ -10137,6 +10701,12 @@ "es-abstract": "^1.17.0-next.1" } }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, "regexpu-core": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", @@ -10350,6 +10920,16 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -10386,6 +10966,12 @@ "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", "dev": true }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -10401,6 +10987,15 @@ "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", "dev": true }, + "rxjs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", + "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -10703,6 +11298,17 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -11254,6 +11860,18 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -11339,12 +11957,24 @@ "minimatch": "^3.0.4" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "dev": true }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -11370,6 +12000,15 @@ "setimmediate": "^1.0.4" } }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -12367,6 +13006,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", diff --git a/package.json b/package.json index 2629c75..69e2990 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,9 @@ "babel-core": "^6.26.3", "babel-loader": "^8.1.0", "babel-polyfill": "^6.26.0", + "eslint": "^7.2.0", "file-loader": "^6.0.0", + "font-awesome": "^4.7.0", "jasmine": "^3.5.0", "jest": "^25.2.3", "jest-puppeteer": "^4.4.0", diff --git a/src/app.js b/src/app.js index 0856b32..f80d5d5 100644 --- a/src/app.js +++ b/src/app.js @@ -1,28 +1,13 @@ -import $ from 'jquery'; +import { setup } from './setup'; +import Array from './arrayHelpers'; +import 'bootstrap'; -window.$ = $ -window.jQuery = $ - -// require('../node_modules/jquery-ui-dist!jquery-ui-dist/jquery-ui.css'); import 'jquery-ui' import 'jquery-ui-dist/jquery-ui' import 'bootstrap' -// jquery-ui theme -// require('../node_modules/jquery-ui-dist/jquery-ui.css'); -// require('../node_modules/jquery-ui-dist/jquery-ui.theme.css'); - -// jquery-ui theme -// require.context('file-loader?name=[path][name].[ext]&context=node_modules/jquery-ui-dist!jquery-ui-dist', true, /jquery-ui.css/); -// require.context('file-loader?name=[path][name].[ext]&context=node_modules/jquery-ui-dist!jquery-ui-dist', true, /jquery-ui\.theme\.css/); -// import 'jquery-ujs'; -// var Turbolinks = require("turbolinks"); -// Turbolinks.start(); +document.addEventListener('DOMContentLoaded', () => { + setup(); +}); -// import -import { setup } from './circuit' -import { AndGate } from './module' -import { Array } from './arrayHelpers' -window.setup = setup -window["AndGate"] = AndGate -window["Array"] = Array \ No newline at end of file +window.Array = Array; diff --git a/src/arrayHelpers.js b/src/arrayHelpers.js index 5c2f69c..06c5818 100644 --- a/src/arrayHelpers.js +++ b/src/arrayHelpers.js @@ -1,7 +1,10 @@ -export var Array = window.Array +/* eslint-disable func-names */ +/* eslint-disable no-global-assign */ +/* eslint-disable no-extend-native */ +export default Array = window.Array; Array.prototype.clean = function (deleteValue) { - for (var i = 0; i < this.length; i++) { - if (this[i] == deleteValue) { + for (let i = 0; i < this.length; i++) { + if (this[i] === deleteValue) { this.splice(i, 1); i--; } @@ -10,15 +13,15 @@ Array.prototype.clean = function (deleteValue) { }; // Following function need to be improved -Array.prototype.extend = function (other_array) { +Array.prototype.extend = function (otherArray) { /* you should include a test to check whether other_array really is an array */ - other_array.forEach(function (v) { - this.push(v) + otherArray.forEach(function (v) { + this.push(v); }, this); -} +}; // Following function need to be improved -//fn to check if an elem is in an array +// fn to check if an elem is in an array Array.prototype.contains = function (value) { - return this.indexOf(value) > -1 -}; \ No newline at end of file + return this.indexOf(value) > -1; +}; diff --git a/src/backgroundArea.js b/src/backgroundArea.js index 239f670..9447743 100644 --- a/src/backgroundArea.js +++ b/src/backgroundArea.js @@ -1,16 +1,17 @@ -import { width,height } from "./circuit"; -import { dots } from "./canvasApi"; -export const backgroundArea = { - canvas: document.getElementById("backgroundArea"), +import { dots } from './canvasApi'; + +let backgroundArea; +export default backgroundArea = { + canvas: document.getElementById('backgroundArea'), setup() { - this.canvas = document.getElementById("backgroundArea") + this.canvas = document.getElementById('backgroundArea'); this.canvas.width = width; this.canvas.height = height; - this.context = this.canvas.getContext("2d"); + this.context = this.canvas.getContext('2d'); dots(true, false); }, clear() { if (!this.context) return; this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - } -} + }, +}; diff --git a/src/canvas2svg.js b/src/canvas2svg.js index 1823d35..0b08f8e 100644 --- a/src/canvas2svg.js +++ b/src/canvas2svg.js @@ -1,26 +1,28 @@ -"use strict"; -var STYLES, ctx, CanvasGradient, CanvasPattern, namedEntities; +let STYLES; let ctx; let CanvasGradient; let CanvasPattern; var + namedEntities; -//helper function to format a string +// helper function to format a string function format(str, args) { - var keys = Object.keys(args), i; - for (i=0; i 1) { options = defaultOptions; options.width = arguments[0]; options.height = arguments[1]; - } else if ( !o ) { + } else if (!o) { options = defaultOptions; } else { options = o; } if (!(this instanceof ctx)) { - //did someone call this without new? + // did someone call this without new? return new ctx(options); } - //setup options + // setup options this.width = options.width || defaultOptions.width; this.height = options.height || defaultOptions.height; this.enableMirroring = options.enableMirroring !== undefined ? options.enableMirroring : defaultOptions.enableMirroring; - this.canvas = this; ///point back to this instance! + this.canvas = this; // /point back to this instance! this.__document = options.document || document; // allow passing in an existing context to wrap around @@ -236,31 +247,31 @@ ctx = function (o) { if (options.ctx) { this.__ctx = options.ctx; } else { - this.__canvas = this.__document.createElement("canvas"); - this.__ctx = this.__canvas.getContext("2d"); + this.__canvas = this.__document.createElement('canvas'); + this.__ctx = this.__canvas.getContext('2d'); } this.__setDefaultStyles(); this.__stack = [this.__getStyleState()]; this.__groupStack = []; - //the root svg element - this.__root = this.__document.createElementNS("http://www.w3.org/2000/svg", "svg"); - this.__root.setAttribute("version", 1.1); - this.__root.setAttribute("xmlns", "http://www.w3.org/2000/svg"); - this.__root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink"); - this.__root.setAttribute("width", this.width); - this.__root.setAttribute("height", this.height); + // the root svg element + this.__root = this.__document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + this.__root.setAttribute('version', 1.1); + this.__root.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + this.__root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'); + this.__root.setAttribute('width', this.width); + this.__root.setAttribute('height', this.height); - //make sure we don't generate the same ids in defs + // make sure we don't generate the same ids in defs this.__ids = {}; - //defs tag - this.__defs = this.__document.createElementNS("http://www.w3.org/2000/svg", "defs"); + // defs tag + this.__defs = this.__document.createElementNS('http://www.w3.org/2000/svg', 'defs'); this.__root.appendChild(this.__defs); - //also add a group child. the svg element can't use the transform attribute - this.__currentElement = this.__document.createElementNS("http://www.w3.org/2000/svg", "g"); + // also add a group child. the svg element can't use the transform attribute + this.__currentElement = this.__document.createElementNS('http://www.w3.org/2000/svg', 'g'); this.__root.appendChild(this.__currentElement); }; @@ -270,18 +281,19 @@ ctx = function (o) { * @private */ ctx.prototype.__createElement = function (elementName, properties, resetFill) { - if (typeof properties === "undefined") { + if (typeof properties === 'undefined') { properties = {}; } - var element = this.__document.createElementNS("http://www.w3.org/2000/svg", elementName), - keys = Object.keys(properties), i, key; + const element = this.__document.createElementNS('http://www.w3.org/2000/svg', elementName); + const keys = Object.keys(properties); let i; var + key; if (resetFill) { - //if fill or stroke is not specified, the svg element should not display. By default SVG's fill is black. - element.setAttribute("fill", "none"); - element.setAttribute("stroke", "none"); + // if fill or stroke is not specified, the svg element should not display. By default SVG's fill is black. + element.setAttribute('fill', 'none'); + element.setAttribute('stroke', 'none'); } - for (i=0; i { + node.setAttribute(type, ''); + }); } - var keys = Object.keys(STYLES), i, style, value, id, regex, matches; + const keys = Object.keys(STYLES); let i; let style; let value; let id; let regex; var + matches; for (i = 0; i < keys.length; i++) { style = STYLES[keys[i]]; value = this[keys[i]]; if (style.apply) { - //is this a gradient or pattern? + // is this a gradient or pattern? if (value instanceof CanvasPattern) { - //pattern + // pattern if (value.__ctx) { - //copy over defs - while(value.__ctx.__defs.childNodes.length) { - id = value.__ctx.__defs.childNodes[0].getAttribute("id"); + // copy over defs + while (value.__ctx.__defs.childNodes.length) { + id = value.__ctx.__defs.childNodes[0].getAttribute('id'); this.__ids[id] = id; this.__defs.appendChild(value.__ctx.__defs.childNodes[0]); } } - currentElement.setAttribute(style.apply, format("url(#{id})", {id:value.__root.getAttribute("id")})); - } - else if (value instanceof CanvasGradient) { - //gradient - currentElement.setAttribute(style.apply, format("url(#{id})", {id:value.__root.getAttribute("id")})); - } else if (style.apply.indexOf(type)!==-1 && style.svg !== value) { - if ((style.svgAttr === "stroke" || style.svgAttr === "fill") && value.indexOf("rgba") !== -1) { - //separate alpha value, since illustrator can't handle it + currentElement.setAttribute(style.apply, format('url(#{id})', { id: value.__root.getAttribute('id') })); + } else if (value instanceof CanvasGradient) { + // gradient + currentElement.setAttribute(style.apply, format('url(#{id})', { id: value.__root.getAttribute('id') })); + } else if (style.apply.indexOf(type) !== -1 && style.svg !== value) { + if ((style.svgAttr === 'stroke' || style.svgAttr === 'fill') && value.indexOf('rgba') !== -1) { + // separate alpha value, since illustrator can't handle it regex = /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi; matches = regex.exec(value); - currentElement.setAttribute(style.svgAttr, format("rgb({r},{g},{b})", {r:matches[1], g:matches[2], b:matches[3]})); - //should take globalAlpha here - var opacity = matches[4]; - var globalAlpha = this.globalAlpha; + currentElement.setAttribute(style.svgAttr, format('rgb({r},{g},{b})', { r: matches[1], g: matches[2], b: matches[3] })); + // should take globalAlpha here + let opacity = matches[4]; + const { globalAlpha } = this; if (globalAlpha != null) { opacity *= globalAlpha; } - currentElement.setAttribute(style.svgAttr+"-opacity", opacity); + currentElement.setAttribute(`${style.svgAttr}-opacity`, opacity); } else { - var attr = style.svgAttr; + let attr = style.svgAttr; if (keys[i] === 'globalAlpha') { - attr = type+'-'+style.svgAttr; + attr = `${type}-${style.svgAttr}`; if (currentElement.getAttribute(attr)) { - //fill-opacity or stroke-opacity has already been set by stroke or fill. + // fill-opacity or stroke-opacity has already been set by stroke or fill. continue; } } - //otherwise only update attribute if right type, and not svg default + // otherwise only update attribute if right type, and not svg default currentElement.setAttribute(attr, value); } } @@ -401,11 +416,10 @@ ctx.prototype.__applyStyleToCurrentElement = function (type) { */ ctx.prototype.__closestGroupOrSvg = function (node) { node = node || this.__currentElement; - if (node.nodeName === "g" || node.nodeName === "svg") { + if (node.nodeName === 'g' || node.nodeName === 'svg') { return node; - } else { - return this.__closestGroupOrSvg(node.parentNode); } + return this.__closestGroupOrSvg(node.parentNode); }; /** @@ -415,22 +429,23 @@ ctx.prototype.__closestGroupOrSvg = function (node) { * @return serialized svg */ ctx.prototype.getSerializedSvg = function (fixNamedEntities) { - var serialized = new XMLSerializer().serializeToString(this.__root), - keys, i, key, value, regexp, xmlns; + let serialized = new XMLSerializer().serializeToString(this.__root); + let keys; let i; let key; let value; let regexp; var + xmlns; - //IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly + // IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi; if (xmlns.test(serialized)) { - serialized = serialized.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink'); + serialized = serialized.replace('xmlns="http://www.w3.org/2000/svg', 'xmlns:xlink="http://www.w3.org/1999/xlink'); } if (fixNamedEntities) { keys = Object.keys(namedEntities); - //loop over each named entity and replace with the proper equivalent. - for (i=0; i 0) { - if (this.__currentElement.nodeName === "path") { - if (!this.__currentElementsToStyle) this.__currentElementsToStyle = {element: parent, children: []}; - this.__currentElementsToStyle.children.push(this.__currentElement) + if (this.__currentElement.nodeName === 'path') { + if (!this.__currentElementsToStyle) this.__currentElementsToStyle = { element: parent, children: [] }; + this.__currentElementsToStyle.children.push(this.__currentElement); this.__applyCurrentDefaultPath(); } - var group = this.__createElement("g"); + const group = this.__createElement('g'); parent.appendChild(group); this.__currentElement = group; } - var transform = this.__currentElement.getAttribute("transform"); + let transform = this.__currentElement.getAttribute('transform'); if (transform) { - transform += " "; + transform += ' '; } else { - transform = ""; + transform = ''; } transform += t; - this.__currentElement.setAttribute("transform", transform); + this.__currentElement.setAttribute('transform', transform); }; /** @@ -509,43 +524,46 @@ ctx.prototype.scale = function (x, y) { if (y === undefined) { y = x; } - this.__addTransform(format("scale({x},{y})", {x:x, y:y})); + this.__addTransform(format('scale({x},{y})', { x, y })); }; /** * rotates the current element */ ctx.prototype.rotate = function (angle) { - var degrees = (angle * 180 / Math.PI); - this.__addTransform(format("rotate({angle},{cx},{cy})", {angle:degrees, cx:0, cy:0})); + const degrees = (angle * 180 / Math.PI); + this.__addTransform(format('rotate({angle},{cx},{cy})', { angle: degrees, cx: 0, cy: 0 })); }; /** * translates the current element */ ctx.prototype.translate = function (x, y) { - this.__addTransform(format("translate({x},{y})", {x:x,y:y})); + this.__addTransform(format('translate({x},{y})', { x, y })); }; /** * applies a transform to the current element */ ctx.prototype.transform = function (a, b, c, d, e, f) { - this.__addTransform(format("matrix({a},{b},{c},{d},{e},{f})", {a:a, b:b, c:c, d:d, e:e, f:f})); + this.__addTransform(format('matrix({a},{b},{c},{d},{e},{f})', { + a, b, c, d, e, f, + })); }; /** * Create a new Path Element */ ctx.prototype.beginPath = function () { - var path, parent; + let path; var + parent; // Note that there is only one current default path, it is not part of the drawing state. // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path - this.__currentDefaultPath = ""; + this.__currentDefaultPath = ''; this.__currentPosition = {}; - path = this.__createElement("path", {}, true); + path = this.__createElement('path', {}, true); parent = this.__closestGroupOrSvg(); parent.appendChild(path); this.__currentElement = path; @@ -556,11 +574,11 @@ ctx.prototype.beginPath = function () { * @private */ ctx.prototype.__applyCurrentDefaultPath = function () { - var currentElement = this.__currentElement; - if (currentElement.nodeName === "path") { - currentElement.setAttribute("d", this.__currentDefaultPath); + const currentElement = this.__currentElement; + if (currentElement.nodeName === 'path') { + currentElement.setAttribute('d', this.__currentDefaultPath); } else { - console.error("Attempted to apply path command to node", currentElement.nodeName); + console.error('Attempted to apply path command to node', currentElement.nodeName); } }; @@ -569,7 +587,7 @@ ctx.prototype.__applyCurrentDefaultPath = function () { * @private */ ctx.prototype.__addPathCommand = function (command) { - this.__currentDefaultPath += " "; + this.__currentDefaultPath += ' '; this.__currentDefaultPath += command; }; @@ -577,14 +595,14 @@ ctx.prototype.__addPathCommand = function (command) { * Adds the move command to the current path element, * if the currentPathElement is not empty create a new path element */ -ctx.prototype.moveTo = function (x,y) { - if (this.__currentElement.nodeName !== "path") { +ctx.prototype.moveTo = function (x, y) { + if (this.__currentElement.nodeName !== 'path') { this.beginPath(); } // creates a new subpath with the given point - this.__currentPosition = {x: x, y: y}; - this.__addPathCommand(format("M {x} {y}", {x:x, y:y})); + this.__currentPosition = { x, y }; + this.__addPathCommand(format('M {x} {y}', { x, y })); }; /** @@ -592,7 +610,7 @@ ctx.prototype.moveTo = function (x,y) { */ ctx.prototype.closePath = function () { if (this.__currentDefaultPath) { - this.__addPathCommand("Z"); + this.__addPathCommand('Z'); } }; @@ -600,11 +618,11 @@ ctx.prototype.closePath = function () { * Adds a line to command */ ctx.prototype.lineTo = function (x, y) { - this.__currentPosition = {x: x, y: y}; + this.__currentPosition = { x, y }; if (this.__currentDefaultPath.indexOf('M') > -1) { - this.__addPathCommand(format("L {x} {y}", {x:x, y:y})); + this.__addPathCommand(format('L {x} {y}', { x, y })); } else { - this.__addPathCommand(format("M {x} {y}", {x:x, y:y})); + this.__addPathCommand(format('M {x} {y}', { x, y })); } }; @@ -612,25 +630,30 @@ ctx.prototype.lineTo = function (x, y) { * Add a bezier command */ ctx.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { - this.__currentPosition = {x: x, y: y}; - this.__addPathCommand(format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", - {cp1x:cp1x, cp1y:cp1y, cp2x:cp2x, cp2y:cp2y, x:x, y:y})); + this.__currentPosition = { x, y }; + this.__addPathCommand(format('C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}', + { + cp1x, cp1y, cp2x, cp2y, x, y, + })); }; /** * Adds a quadratic curve to command */ ctx.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { - this.__currentPosition = {x: x, y: y}; - this.__addPathCommand(format("Q {cpx} {cpy} {x} {y}", {cpx:cpx, cpy:cpy, x:x, y:y})); + this.__currentPosition = { x, y }; + this.__addPathCommand(format('Q {cpx} {cpy} {x} {y}', { + cpx, cpy, x, y, + })); }; /** * Return a new normalized vector of given vector + * @category c2s */ -var normalize = function (vector) { - var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); +const normalize = function (vector) { + const len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); return [vector[0] / len, vector[1] / len]; }; @@ -641,17 +664,17 @@ var normalize = function (vector) { */ ctx.prototype.arcTo = function (x1, y1, x2, y2, radius) { // Let the point (x0, y0) be the last point in the subpath. - var x0 = this.__currentPosition && this.__currentPosition.x; - var y0 = this.__currentPosition && this.__currentPosition.y; + const x0 = this.__currentPosition && this.__currentPosition.x; + const y0 = this.__currentPosition && this.__currentPosition.y; // First ensure there is a subpath for (x1, y1). - if (typeof x0 == "undefined" || typeof y0 == "undefined") { + if (typeof x0 === 'undefined' || typeof y0 === 'undefined') { return; } // Negative values for radius must cause the implementation to throw an IndexSizeError exception. if (radius < 0) { - throw new Error("IndexSizeError: The radius provided (" + radius + ") is negative."); + throw new Error(`IndexSizeError: The radius provided (${radius}) is negative.`); } // If the point (x0, y0) is equal to the point (x1, y1), @@ -669,8 +692,8 @@ ctx.prototype.arcTo = function (x1, y1, x2, y2, radius) { // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line, // then the method must add the point (x1, y1) to the subpath, // and connect that point to the previous point (x0, y0) by a straight line. - var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); - var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); + const unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); + const unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); if (unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === unit_vec_p1_p0[1] * unit_vec_p1_p2[0]) { this.lineTo(x1, y1); return; @@ -682,45 +705,44 @@ ctx.prototype.arcTo = function (x1, y1, x2, y2, radius) { // The points at which this circle touches these two lines are called the start and end tangent points respectively. // note that both vectors are unit vectors, so the length is 1 - var cos = (unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1]); - var theta = Math.acos(Math.abs(cos)); + const cos = (unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1]); + const theta = Math.acos(Math.abs(cos)); // Calculate origin - var unit_vec_p1_origin = normalize([ + const unit_vec_p1_origin = normalize([ unit_vec_p1_p0[0] + unit_vec_p1_p2[0], - unit_vec_p1_p0[1] + unit_vec_p1_p2[1] + unit_vec_p1_p0[1] + unit_vec_p1_p2[1], ]); - var len_p1_origin = radius / Math.sin(theta / 2); - var x = x1 + len_p1_origin * unit_vec_p1_origin[0]; - var y = y1 + len_p1_origin * unit_vec_p1_origin[1]; + const len_p1_origin = radius / Math.sin(theta / 2); + const x = x1 + len_p1_origin * unit_vec_p1_origin[0]; + const y = y1 + len_p1_origin * unit_vec_p1_origin[1]; // Calculate start angle and end angle // rotate 90deg clockwise (note that y axis points to its down) - var unit_vec_origin_start_tangent = [ + const unit_vec_origin_start_tangent = [ -unit_vec_p1_p0[1], - unit_vec_p1_p0[0] + unit_vec_p1_p0[0], ]; // rotate 90deg counter clockwise (note that y axis points to its down) - var unit_vec_origin_end_tangent = [ + const unit_vec_origin_end_tangent = [ unit_vec_p1_p2[1], - -unit_vec_p1_p2[0] + -unit_vec_p1_p2[0], ]; - var getAngle = function (vector) { + const getAngle = function (vector) { // get angle (clockwise) between vector and (1, 0) - var x = vector[0]; - var y = vector[1]; + const x = vector[0]; + const y = vector[1]; if (y >= 0) { // note that y axis points to its down return Math.acos(x); - } else { - return -Math.acos(x); } + return -Math.acos(x); }; - var startAngle = getAngle(unit_vec_origin_start_tangent); - var endAngle = getAngle(unit_vec_origin_end_tangent); + const startAngle = getAngle(unit_vec_origin_start_tangent); + const endAngle = getAngle(unit_vec_origin_end_tangent); // Connect the point (x0, y0) to the start tangent point by a straight line this.lineTo(x + unit_vec_origin_start_tangent[0] * radius, - y + unit_vec_origin_start_tangent[1] * radius); + y + unit_vec_origin_start_tangent[1] * radius); // Connect the start tangent point to the end tangent point by arc // and adding the end tangent point to the subpath. @@ -731,35 +753,35 @@ ctx.prototype.arcTo = function (x1, y1, x2, y2, radius) { * Sets the stroke property on the current element */ ctx.prototype.stroke = function () { - if (this.__currentElement.nodeName === "path") { - this.__currentElement.setAttribute("paint-order", "fill stroke markers"); + if (this.__currentElement.nodeName === 'path') { + this.__currentElement.setAttribute('paint-order', 'fill stroke markers'); } this.__applyCurrentDefaultPath(); - this.__applyStyleToCurrentElement("stroke"); + this.__applyStyleToCurrentElement('stroke'); }; /** * Sets fill properties on the current element */ ctx.prototype.fill = function () { - if (this.__currentElement.nodeName === "path") { - this.__currentElement.setAttribute("paint-order", "stroke fill markers"); + if (this.__currentElement.nodeName === 'path') { + this.__currentElement.setAttribute('paint-order', 'stroke fill markers'); } this.__applyCurrentDefaultPath(); - this.__applyStyleToCurrentElement("fill"); + this.__applyStyleToCurrentElement('fill'); }; /** * Adds a rectangle to the path. */ ctx.prototype.rect = function (x, y, width, height) { - if (this.__currentElement.nodeName !== "path") { + if (this.__currentElement.nodeName !== 'path') { this.beginPath(); } this.moveTo(x, y); - this.lineTo(x+width, y); - this.lineTo(x+width, y+height); - this.lineTo(x, y+height); + this.lineTo(x + width, y); + this.lineTo(x + width, y + height); + this.lineTo(x, y + height); this.lineTo(x, y); this.closePath(); }; @@ -769,17 +791,18 @@ ctx.prototype.rect = function (x, y, width, height) { * adds a rectangle element */ ctx.prototype.fillRect = function (x, y, width, height) { - var rect, parent; - rect = this.__createElement("rect", { - x : x, - y : y, - width : width, - height : height + let rect; var + parent; + rect = this.__createElement('rect', { + x, + y, + width, + height, }, true); parent = this.__closestGroupOrSvg(); parent.appendChild(rect); this.__currentElement = rect; - this.__applyStyleToCurrentElement("fill"); + this.__applyStyleToCurrentElement('fill'); }; /** @@ -790,17 +813,18 @@ ctx.prototype.fillRect = function (x, y, width, height) { * @param height */ ctx.prototype.strokeRect = function (x, y, width, height) { - var rect, parent; - rect = this.__createElement("rect", { - x : x, - y : y, - width : width, - height : height + let rect; var + parent; + rect = this.__createElement('rect', { + x, + y, + width, + height, }, true); parent = this.__closestGroupOrSvg(); parent.appendChild(rect); this.__currentElement = rect; - this.__applyStyleToCurrentElement("stroke"); + this.__applyStyleToCurrentElement('stroke'); }; @@ -810,17 +834,17 @@ ctx.prototype.strokeRect = function (x, y, width, height) { * 2. remove all the childNodes of the root g element */ ctx.prototype.__clearCanvas = function () { - var current = this.__closestGroupOrSvg(), - transform = current.getAttribute("transform"); - var rootGroup = this.__root.childNodes[1]; - var childNodes = rootGroup.childNodes; - for (var i = childNodes.length - 1; i >= 0; i--) { + const current = this.__closestGroupOrSvg(); + const transform = current.getAttribute('transform'); + const rootGroup = this.__root.childNodes[1]; + const { childNodes } = rootGroup; + for (let i = childNodes.length - 1; i >= 0; i--) { if (childNodes[i]) { rootGroup.removeChild(childNodes[i]); } } this.__currentElement = rootGroup; - //reset __groupStack as all the child group nodes are all removed. + // reset __groupStack as all the child group nodes are all removed. this.__groupStack = []; if (transform) { this.__addTransform(transform); @@ -831,18 +855,19 @@ ctx.prototype.__clearCanvas = function () { * "Clears" a canvas by just drawing a white rectangle in the current group. */ ctx.prototype.clearRect = function (x, y, width, height) { - //clear entire canvas + // clear entire canvas if (x === 0 && y === 0 && width === this.width && height === this.height) { this.__clearCanvas(); return; } - var rect, parent = this.__closestGroupOrSvg(); - rect = this.__createElement("rect", { - x : x, - y : y, - width : width, - height : height, - fill : "#FFFFFF" + let rect; var + parent = this.__closestGroupOrSvg(); + rect = this.__createElement('rect', { + x, + y, + width, + height, + fill: '#FFFFFF', }, true); parent.appendChild(rect); }; @@ -852,13 +877,13 @@ ctx.prototype.clearRect = function (x, y, width, height) { * Returns a canvas gradient object that has a reference to it's parent def */ ctx.prototype.createLinearGradient = function (x1, y1, x2, y2) { - var grad = this.__createElement("linearGradient", { - id : randomString(this.__ids), - x1 : x1+"px", - x2 : x2+"px", - y1 : y1+"px", - y2 : y2+"px", - "gradientUnits" : "userSpaceOnUse" + const grad = this.__createElement('linearGradient', { + id: randomString(this.__ids), + x1: `${x1}px`, + x2: `${x2}px`, + y1: `${y1}px`, + y2: `${y2}px`, + gradientUnits: 'userSpaceOnUse', }, false); this.__defs.appendChild(grad); return new CanvasGradient(grad, this); @@ -869,18 +894,17 @@ ctx.prototype.createLinearGradient = function (x1, y1, x2, y2) { * Returns a canvas gradient object that has a reference to it's parent def */ ctx.prototype.createRadialGradient = function (x0, y0, r0, x1, y1, r1) { - var grad = this.__createElement("radialGradient", { - id : randomString(this.__ids), - cx : x1+"px", - cy : y1+"px", - r : r1+"px", - fx : x0+"px", - fy : y0+"px", - "gradientUnits" : "userSpaceOnUse" + const grad = this.__createElement('radialGradient', { + id: randomString(this.__ids), + cx: `${x1}px`, + cy: `${y1}px`, + r: `${r1}px`, + fx: `${x0}px`, + fy: `${y0}px`, + gradientUnits: 'userSpaceOnUse', }, false); this.__defs.appendChild(grad); return new CanvasGradient(grad, this); - }; /** @@ -888,23 +912,23 @@ ctx.prototype.createRadialGradient = function (x0, y0, r0, x1, y1, r1) { * @private */ ctx.prototype.__parseFont = function () { - var regex = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\'\"\sa-z0-9]+?)\s*$/i; - var fontPart = regex.exec( this.font ); - var data = { - style : fontPart[1] || 'normal', - size : fontPart[4] || '10px', - family : fontPart[6] || 'sans-serif', + const regex = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\'\"\sa-z0-9]+?)\s*$/i; + const fontPart = regex.exec(this.font); + const data = { + style: fontPart[1] || 'normal', + size: fontPart[4] || '10px', + family: fontPart[6] || 'sans-serif', weight: fontPart[3] || 'normal', - decoration : fontPart[2] || 'normal', - href : null + decoration: fontPart[2] || 'normal', + href: null, }; - //canvas doesn't support underline natively, but we can pass this attribute - if (this.__fontUnderline === "underline") { - data.decoration = "underline"; + // canvas doesn't support underline natively, but we can pass this attribute + if (this.__fontUnderline === 'underline') { + data.decoration = 'underline'; } - //canvas also doesn't support linking, but we can pass this as well + // canvas also doesn't support linking, but we can pass this as well if (this.__fontHref) { data.href = this.__fontHref; } @@ -921,8 +945,8 @@ ctx.prototype.__parseFont = function () { */ ctx.prototype.__wrapTextLink = function (font, element) { if (font.href) { - var a = this.__createElement("a"); - a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", font.href); + const a = this.__createElement('a'); + a.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', font.href); a.appendChild(element); return a; } @@ -938,24 +962,24 @@ ctx.prototype.__wrapTextLink = function (font, element) { * @private */ ctx.prototype.__applyText = function (text, x, y, action) { - var font = this.__parseFont(), - parent = this.__closestGroupOrSvg(), - textElement = this.__createElement("text", { - "font-family" : font.family, - "font-size" : font.size, - "font-style" : font.style, - "font-weight" : font.weight, - "text-decoration" : font.decoration, - "x" : x, - "y" : y, - "text-anchor": getTextAnchor(this.textAlign), - "dominant-baseline": getDominantBaseline(this.textBaseline) - }, true); + const font = this.__parseFont(); + const parent = this.__closestGroupOrSvg(); + const textElement = this.__createElement('text', { + 'font-family': font.family, + 'font-size': font.size, + 'font-style': font.style, + 'font-weight': font.weight, + 'text-decoration': font.decoration, + x, + y, + 'text-anchor': getTextAnchor(this.textAlign), + 'dominant-baseline': getDominantBaseline(this.textBaseline), + }, true); textElement.appendChild(this.__document.createTextNode(text)); this.__currentElement = textElement; this.__applyStyleToCurrentElement(action); - parent.appendChild(this.__wrapTextLink(font,textElement)); + parent.appendChild(this.__wrapTextLink(font, textElement)); }; /** @@ -965,7 +989,7 @@ ctx.prototype.__applyText = function (text, x, y, action) { * @param y */ ctx.prototype.fillText = function (text, x, y) { - this.__applyText(text, x, y, "fill"); + this.__applyText(text, x, y, 'fill'); }; /** @@ -975,7 +999,7 @@ ctx.prototype.fillText = function (text, x, y) { * @param y */ ctx.prototype.strokeText = function (text, x, y) { - this.__applyText(text, x, y, "stroke"); + this.__applyText(text, x, y, 'stroke'); }; /** @@ -996,23 +1020,23 @@ ctx.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwi if (startAngle === endAngle) { return; } - startAngle = startAngle % (2*Math.PI); - endAngle = endAngle % (2*Math.PI); + startAngle %= (2 * Math.PI); + endAngle %= (2 * Math.PI); if (startAngle === endAngle) { - //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle) - endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI); + // circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle) + endAngle = ((endAngle + (2 * Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2 * Math.PI); } - var endX = x+radius*Math.cos(endAngle), - endY = y+radius*Math.sin(endAngle), - startX = x+radius*Math.cos(startAngle), - startY = y+radius*Math.sin(startAngle), - sweepFlag = counterClockwise ? 0 : 1, - largeArcFlag = 0, - diff = endAngle - startAngle; + const endX = x + radius * Math.cos(endAngle); + const endY = y + radius * Math.sin(endAngle); + const startX = x + radius * Math.cos(startAngle); + const startY = y + radius * Math.sin(startAngle); + const sweepFlag = counterClockwise ? 0 : 1; + let largeArcFlag = 0; + let diff = endAngle - startAngle; // https://github.com/gliffy/canvas2svg/issues/4 if (diff < 0) { - diff += 2*Math.PI; + diff += 2 * Math.PI; } if (counterClockwise) { @@ -1022,37 +1046,38 @@ ctx.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwi } this.lineTo(startX, startY); - this.__addPathCommand(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", - {rx:radius, ry:radius, xAxisRotation:0, largeArcFlag:largeArcFlag, sweepFlag:sweepFlag, endX:endX, endY:endY})); + this.__addPathCommand(format('A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}', + { + rx: radius, ry: radius, xAxisRotation: 0, largeArcFlag, sweepFlag, endX, endY, + })); - this.__currentPosition = {x: endX, y: endY}; + this.__currentPosition = { x: endX, y: endY }; }; /** * Generates a ClipPath from the clip command. */ ctx.prototype.clip = function () { - var group = this.__closestGroupOrSvg(), - clipPath = this.__createElement("clipPath"), - id = randomString(this.__ids), - newGroup = this.__createElement("g"); + const group = this.__closestGroupOrSvg(); + const clipPath = this.__createElement('clipPath'); + const id = randomString(this.__ids); + const newGroup = this.__createElement('g'); this.__applyCurrentDefaultPath(); group.removeChild(this.__currentElement); - clipPath.setAttribute("id", id); + clipPath.setAttribute('id', id); clipPath.appendChild(this.__currentElement); this.__defs.appendChild(clipPath); - //set the clip path to this group - group.setAttribute("clip-path", format("url(#{id})", {id:id})); + // set the clip path to this group + group.setAttribute('clip-path', format('url(#{id})', { id })); - //clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations + // clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations // to this path group.appendChild(newGroup); this.__currentElement = newGroup; - }; /** @@ -1061,11 +1086,12 @@ ctx.prototype.clip = function () { * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage */ ctx.prototype.drawImage = function () { - //convert arguments to a real array - var args = Array.prototype.slice.call(arguments), - image=args[0], - dx, dy, dw, dh, sx=0, sy=0, sw, sh, parent, svg, defs, group, - currentElement, svgImage, canvas, context, id; + // convert arguments to a real array + const args = Array.prototype.slice.call(arguments); + let image = args[0]; + let dx; let dy; let dw; let dh; let sx = 0; let sy = 0; let sw; let sh; let parent; let svg; let defs; let group; + let currentElement; let svgImage; let canvas; let context; var + id; if (args.length === 3) { dx = args[1]; @@ -1091,56 +1117,56 @@ ctx.prototype.drawImage = function () { dw = args[7]; dh = args[8]; } else { - throw new Error("Inavlid number of arguments passed to drawImage: " + arguments.length); + throw new Error(`Inavlid number of arguments passed to drawImage: ${arguments.length}`); } parent = this.__closestGroupOrSvg(); currentElement = this.__currentElement; - var translateDirective = "translate(" + dx + ", " + dy + ")"; + const translateDirective = `translate(${dx}, ${dy})`; if (image instanceof ctx) { - //canvas2svg mock canvas context. In the future we may want to clone nodes instead. - //also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context. + // canvas2svg mock canvas context. In the future we may want to clone nodes instead. + // also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context. svg = image.getSvg().cloneNode(true); if (svg.childNodes && svg.childNodes.length > 1) { defs = svg.childNodes[0]; - while(defs.childNodes.length) { - id = defs.childNodes[0].getAttribute("id"); + while (defs.childNodes.length) { + id = defs.childNodes[0].getAttribute('id'); this.__ids[id] = id; this.__defs.appendChild(defs.childNodes[0]); } group = svg.childNodes[1]; if (group) { - //save original transform - var originTransform = group.getAttribute("transform"); - var transformDirective; + // save original transform + const originTransform = group.getAttribute('transform'); + let transformDirective; if (originTransform) { - transformDirective = originTransform+" "+translateDirective; + transformDirective = `${originTransform} ${translateDirective}`; } else { transformDirective = translateDirective; } - group.setAttribute("transform", transformDirective); + group.setAttribute('transform', transformDirective); parent.appendChild(group); } } - } else if (image.nodeName === "CANVAS" || image.nodeName === "IMG") { - //canvas or image - svgImage = this.__createElement("image"); - svgImage.setAttribute("width", dw); - svgImage.setAttribute("height", dh); - svgImage.setAttribute("preserveAspectRatio", "none"); + } else if (image.nodeName === 'CANVAS' || image.nodeName === 'IMG') { + // canvas or image + svgImage = this.__createElement('image'); + svgImage.setAttribute('width', dw); + svgImage.setAttribute('height', dh); + svgImage.setAttribute('preserveAspectRatio', 'none'); if (sx || sy || sw !== image.width || sh !== image.height) { - //crop the image using a temporary canvas - canvas = this.__document.createElement("canvas"); + // crop the image using a temporary canvas + canvas = this.__document.createElement('canvas'); canvas.width = dw; canvas.height = dh; - context = canvas.getContext("2d"); + context = canvas.getContext('2d'); context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh); image = canvas; } - svgImage.setAttribute("transform", translateDirective); - svgImage.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", - image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src")); + svgImage.setAttribute('transform', translateDirective); + svgImage.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + image.nodeName === 'CANVAS' ? image.toDataURL() : image.getAttribute('src')); parent.appendChild(svgImage); } }; @@ -1149,17 +1175,17 @@ ctx.prototype.drawImage = function () { * Generates a pattern tag */ ctx.prototype.createPattern = function (image, repetition) { - var pattern = this.__document.createElementNS("http://www.w3.org/2000/svg", "pattern"), id = randomString(this.__ids), - img; - pattern.setAttribute("id", id); - pattern.setAttribute("width", image.width); - pattern.setAttribute("height", image.height); - if (image.nodeName === "CANVAS" || image.nodeName === "IMG") { - img = this.__document.createElementNS("http://www.w3.org/2000/svg", "image"); - img.setAttribute("width", image.width); - img.setAttribute("height", image.height); - img.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", - image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src")); + const pattern = this.__document.createElementNS('http://www.w3.org/2000/svg', 'pattern'); const id = randomString(this.__ids); + let img; + pattern.setAttribute('id', id); + pattern.setAttribute('width', image.width); + pattern.setAttribute('height', image.height); + if (image.nodeName === 'CANVAS' || image.nodeName === 'IMG') { + img = this.__document.createElementNS('http://www.w3.org/2000/svg', 'image'); + img.setAttribute('width', image.width); + img.setAttribute('height', image.height); + img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + image.nodeName === 'CANVAS' ? image.toDataURL() : image.getAttribute('src')); pattern.appendChild(img); this.__defs.appendChild(pattern); } else if (image instanceof ctx) { @@ -1171,7 +1197,7 @@ ctx.prototype.createPattern = function (image, repetition) { ctx.prototype.setLineDash = function (dashArray) { if (dashArray && dashArray.length > 0) { - this.lineDash = dashArray.join(","); + this.lineDash = dashArray.join(','); } else { this.lineDash = null; } @@ -1187,13 +1213,12 @@ ctx.prototype.putImageData = function () {}; ctx.prototype.globalCompositeOperation = function () {}; ctx.prototype.setTransform = function () {}; -//add options for alternative namespace -if (typeof window === "object") { +// add options for alternative namespace +if (typeof window === 'object') { window.C2S = ctx; } // CommonJS/Browserify -if (typeof module === "object" && typeof module.exports === "object") { +if (typeof module === 'object' && typeof module.exports === 'object') { module.exports = ctx; } - diff --git a/src/canvasApi.js b/src/canvasApi.js index 648175a..7cfb1aa 100644 --- a/src/canvasApi.js +++ b/src/canvasApi.js @@ -1,40 +1,74 @@ -import { backgroundArea } from "./backgroundArea"; -import { simulationArea } from "./simulationArea"; -import { findDimensions } from "./engine"; +/* eslint-disable no-param-reassign */ +import backgroundArea from './backgroundArea'; +import simulationArea from './simulationArea'; +import miniMapArea, { removeMiniMap, updatelastMinimapShown } from './minimap'; + +const unit = 10; + +export function findDimensions(scope = globalScope) { + let totalObjects = 0; + simulationArea.minWidth = undefined; + simulationArea.maxWidth = undefined; + simulationArea.minHeight = undefined; + simulationArea.maxHeight = undefined; + for (let i = 0; i < updateOrder.length; i++) { + if (updateOrder[i] !== 'wires') { + for (let j = 0; j < scope[updateOrder[i]].length; j++) { + totalObjects += 1; + const obj = scope[updateOrder[i]][j]; + if (totalObjects === 1) { + simulationArea.minWidth = obj.absX(); + simulationArea.minHeight = obj.absY(); + simulationArea.maxWidth = obj.absX(); + simulationArea.maxHeight = obj.absY(); + } + if (obj.objectType !== 'Node') { + if (obj.y - obj.upDimensionY < simulationArea.minHeight) { simulationArea.minHeight = obj.y - obj.upDimensionY; } + if (obj.y + obj.downDimensionY > simulationArea.maxHeight) { simulationArea.maxHeight = obj.y + obj.downDimensionY; } + if (obj.x - obj.leftDimensionX < simulationArea.minWidth) { simulationArea.minWidth = obj.x - obj.leftDimensionX; } + if (obj.x + obj.rightDimensionX > simulationArea.maxWidth) { simulationArea.maxWidth = obj.x + obj.rightDimensionX; } + } else { + if (obj.absY() < simulationArea.minHeight) { simulationArea.minHeight = obj.absY(); } + if (obj.absY() > simulationArea.maxHeight) { simulationArea.maxHeight = obj.absY(); } + if (obj.absX() < simulationArea.minWidth) { simulationArea.minWidth = obj.absX(); } + if (obj.absX() > simulationArea.maxWidth) { simulationArea.maxWidth = obj.absX(); } + } + } + } + } + simulationArea.objectList = updateOrder; +} + + // Function used to change the zoom level wrt to a point // fn to change scale (zoom) - It also shifts origin so that the position // of the object in focus doesn't change -window.lastMiniMapShown = undefined - - export function changeScale(delta, xx, yy, method = 1) { - // method = 3/2 - Zoom wrt center of screen // method = 1 - Zoom wrt position of mouse // Otherwise zoom wrt to selected object - if (method == 3) { - xx = (width / 2 - globalScope.ox) / globalScope.scale - yy = (height / 2 - globalScope.oy) / globalScope.scale - } - else if (xx === undefined || yy === undefined || xx == 'zoomButton' || yy == 'zoomButton') { - if (simulationArea.lastSelected && simulationArea.lastSelected.objectType != "Wire") { // selected object + if (method === 3) { + xx = (width / 2 - globalScope.ox) / globalScope.scale; + yy = (height / 2 - globalScope.oy) / globalScope.scale; + } else if (xx === undefined || yy === undefined || xx === 'zoomButton' || yy === 'zoomButton') { + if (simulationArea.lastSelected && simulationArea.lastSelected.objectType !== 'Wire') { // selected object xx = simulationArea.lastSelected.x; yy = simulationArea.lastSelected.y; - } else { //mouse location - if (method == 1) { + } else { // mouse location + // eslint-disable-next-line no-lonely-if + if (method === 1) { xx = simulationArea.mouseX; yy = simulationArea.mouseY; - } - else if (method == 2) { - xx = (width / 2 - globalScope.ox) / globalScope.scale - yy = (height / 2 - globalScope.oy) / globalScope.scale + } else if (method === 2) { + xx = (width / 2 - globalScope.ox) / globalScope.scale; + yy = (height / 2 - globalScope.oy) / globalScope.scale; } } } - var oldScale = globalScope.scale; + const oldScale = globalScope.scale; globalScope.scale = Math.max(0.5, Math.min(4 * DPR, globalScope.scale + delta)); globalScope.scale = Math.round(globalScope.scale * 10) / 10; globalScope.ox -= Math.round(xx * (globalScope.scale - oldScale)); // Shift accordingly, so that we zoom wrt to the selected point @@ -47,75 +81,69 @@ export function changeScale(delta, xx, yy, method = 1) { findDimensions(globalScope); miniMapArea.setup(); $('#miniMap').show(); - lastMiniMapShown = new Date().getTime(); + updatelastMinimapShown(); $('#miniMap').show(); setTimeout(removeMiniMap, 2000); } - - } - -//fn to draw Dots on screen -//the function is called only when the zoom level or size of screen changes. +// fn to draw Dots on screen +// the function is called only when the zoom level or size of screen changes. // Otherwise for normal panning, the canvas itself is moved to give the illusion of movement export function dots(dots = true, transparentBackground = false, force = false) { + const scale = unit * globalScope.scale; + const ox = globalScope.ox % scale; // offset + const oy = globalScope.oy % scale; // offset - var scale = unit * globalScope.scale; - var ox = globalScope.ox % scale; //offset - var oy = globalScope.oy % scale; //offset - - document.getElementById("backgroundArea").style.left = (ox - scale) / DPR; - document.getElementById("backgroundArea").style.top = (oy - scale) / DPR; - if (globalScope.scale == simulationArea.prevScale && !force) return; + document.getElementById('backgroundArea').style.left = (ox - scale) / DPR; + document.getElementById('backgroundArea').style.top = (oy - scale) / DPR; + if (globalScope.scale === simulationArea.prevScale && !force) return; if (!backgroundArea.context) return; simulationArea.prevScale = globalScope.scale; - var canvasWidth = backgroundArea.canvas.width; //max X distance - var canvasHeight = backgroundArea.canvas.height; //max Y distance + const canvasWidth = backgroundArea.canvas.width; // max X distance + const canvasHeight = backgroundArea.canvas.height; // max Y distance - var ctx = backgroundArea.context; + const ctx = backgroundArea.context; ctx.beginPath(); backgroundArea.clear(); - ctx.strokeStyle = "#eee"; + ctx.strokeStyle = '#eee'; ctx.lineWidth = 1; if (!transparentBackground) { - ctx.fillStyle = "white"; + ctx.fillStyle = 'white'; ctx.rect(0, 0, canvasWidth, canvasHeight); ctx.fill(); } - var correction = 0.5 * (ctx.lineWidth % 2); - for (var i = 0; i < canvasWidth; i += scale) { + const correction = 0.5 * (ctx.lineWidth % 2); + for (let i = 0; i < canvasWidth; i += scale) { ctx.moveTo(Math.round(i + correction) - correction, 0); ctx.lineTo(Math.round(i + correction) - correction, canvasHeight); } - for (var j = 0; j < canvasHeight; j += scale) { + for (let j = 0; j < canvasHeight; j += scale) { ctx.moveTo(0, Math.round(j + correction) - correction); ctx.lineTo(canvasWidth, Math.round(j + correction) - correction); } ctx.stroke(); - return; - // Old Code // function drawPixel(x, y, r, g, b, a) { - // var index = (x + y * canvasWidth) * 4; + // let index = (x + y * canvasWidth) * 4; // canvasData.data[index + 0] = r; // canvasData.data[index + 1] = g; // canvasData.data[index + 2] = b; // canvasData.data[index + 3] = a; // } // if (dots) { - // var canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight); + // let canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight); // // // - // for (var i = 0 + ox; i < canvasWidth; i += scale) - // for (var j = 0 + oy; j < canvasHeight; j += scale) + // for (let i = 0 + ox; i < canvasWidth; i += scale) + // for (let j = 0 + oy; j < canvasHeight; j += scale) // drawPixel(i, j, 0, 0, 0, 255); // ctx.putImageData(canvasData, 0, 0); // } @@ -126,128 +154,119 @@ export function dots(dots = true, transparentBackground = false, force = false) // direction is used to abstract rotation of everything by a certain angle // Possible values for direction = "RIGHT" (default), "LEFT", "UP", "DOWN" -function bezierCurveTo(x1, y1, x2, y2, x3, y3, xx, yy, dir) { +export function bezierCurveTo(x1, y1, x2, y2, x3, y3, xx, yy, dir) { [x1, y1] = rotate(x1, y1, dir); [x2, y2] = rotate(x2, y2, dir); [x3, y3] = rotate(x3, y3, dir); - var ox = globalScope.ox; - var oy = globalScope.oy; + const { ox } = globalScope; + const { oy } = globalScope; x1 *= globalScope.scale; y1 *= globalScope.scale; x2 *= globalScope.scale; y2 *= globalScope.scale; x3 *= globalScope.scale; y3 *= globalScope.scale; - xx = xx * globalScope.scale; - yy = yy * globalScope.scale; + xx *= globalScope.scale; + yy *= globalScope.scale; + const ctx = simulationArea.context; ctx.bezierCurveTo(Math.round(xx + ox + x1), Math.round(yy + oy + y1), Math.round(xx + ox + x2), Math.round(yy + oy + y2), Math.round(xx + ox + x3), Math.round(yy + oy + y3)); } export function moveTo(ctx, x1, y1, xx, yy, dir, bypass = false) { - var correction = 0.5 * (ctx.lineWidth % 2); - let newX - let newY + const correction = 0.5 * (ctx.lineWidth % 2); + let newX; + let newY; [newX, newY] = rotate(x1, y1, dir); - newX = newX * globalScope.scale; - newY = newY * globalScope.scale; - xx = xx * globalScope.scale; - yy = yy * globalScope.scale; - if (bypass) - ctx.moveTo(xx + globalScope.ox + newX, yy + globalScope.oy + newY); - else - ctx.moveTo(Math.round(xx + globalScope.ox + newX - correction) + correction, Math.round(yy + globalScope.oy + newY - correction) + correction); + newX *= globalScope.scale; + newY *= globalScope.scale; + xx *= globalScope.scale; + yy *= globalScope.scale; + if (bypass) { ctx.moveTo(xx + globalScope.ox + newX, yy + globalScope.oy + newY); } else { ctx.moveTo(Math.round(xx + globalScope.ox + newX - correction) + correction, Math.round(yy + globalScope.oy + newY - correction) + correction); } } export function lineTo(ctx, x1, y1, xx, yy, dir) { - let newX - let newY + let newX; + let newY; - var correction = 0.5 * (ctx.lineWidth % 2); + const correction = 0.5 * (ctx.lineWidth % 2); [newX, newY] = rotate(x1, y1, dir); - newX = newX * globalScope.scale; - newY = newY * globalScope.scale; - xx = xx * globalScope.scale; - yy = yy * globalScope.scale; + newX *= globalScope.scale; + newY *= globalScope.scale; + xx *= globalScope.scale; + yy *= globalScope.scale; ctx.lineTo(Math.round(xx + globalScope.ox + newX - correction) + correction, Math.round(yy + globalScope.oy + newY - correction) + correction); } export function arc(ctx, sx, sy, radius, start, stop, xx, yy, dir) { - - //ox-x of origin, xx- x of element , sx - shift in x from element - let Sx,Sy,newStart,newStop,counterClock - - var correction = 0.5 * (ctx.lineWidth % 2); + // ox-x of origin, xx- x of element , sx - shift in x from element + let Sx; let Sy; let newStart; let newStop; let counterClock; + const correction = 0.5 * (ctx.lineWidth % 2); [Sx, Sy] = rotate(sx, sy, dir); - Sx = Sx * globalScope.scale; - Sy = Sy * globalScope.scale; - xx = xx * globalScope.scale; - yy = yy * globalScope.scale; + Sx *= globalScope.scale; + Sy *= globalScope.scale; + xx *= globalScope.scale; + yy *= globalScope.scale; radius *= globalScope.scale; [newStart, newStop, counterClock] = rotateAngle(start, stop, dir); ctx.arc(Math.round(xx + globalScope.ox + Sx + correction) - correction, Math.round(yy + globalScope.oy + Sy + correction) - correction, Math.round(radius), newStart, newStop, counterClock); } -function arc2(ctx, sx, sy, radius, start, stop, xx, yy, dir) { - - //ox-x of origin, xx- x of element , sx - shift in x from element - - var correction = 0.5 * (ctx.lineWidth % 2); +export function arc2(ctx, sx, sy, radius, start, stop, xx, yy, dir) { + // ox-x of origin, xx- x of element , sx - shift in x from element + let Sx; let Sy; let newStart; let newStop; let counterClock; + const correction = 0.5 * (ctx.lineWidth % 2); [Sx, Sy] = rotate(sx, sy, dir); - Sx = Sx * globalScope.scale; - Sy = Sy * globalScope.scale; - xx = xx * globalScope.scale; - yy = yy * globalScope.scale; + Sx *= globalScope.scale; + Sy *= globalScope.scale; + xx *= globalScope.scale; + yy *= globalScope.scale; radius *= globalScope.scale; [newStart, newStop, counterClock] = rotateAngle(start, stop, dir); - var pi = 0; - if (counterClock) - pi = Math.PI; + let pi = 0; + if (counterClock) { pi = Math.PI; } ctx.arc(Math.round(xx + globalScope.ox + Sx + correction) - correction, Math.round(yy + globalScope.oy + Sy + correction) - correction, Math.round(radius), newStart + pi, newStop + pi); } -export function drawCircle2(ctx, sx, sy, radius, xx, yy, dir) { //ox-x of origin, xx- x of element , sx - shift in x from element - +export function drawCircle2(ctx, sx, sy, radius, xx, yy, dir) { // ox-x of origin, xx- x of element , sx - shift in x from element + let Sx; + let Sy; [Sx, Sy] = rotate(sx, sy, dir); - Sx = Sx * globalScope.scale; - Sy = Sy * globalScope.scale; - xx = xx * globalScope.scale; - yy = yy * globalScope.scale; + Sx *= globalScope.scale; + Sy *= globalScope.scale; + xx *= globalScope.scale; + yy *= globalScope.scale; radius *= globalScope.scale; ctx.arc(Math.round(xx + globalScope.ox + Sx), Math.round(yy + globalScope.oy + Sy), Math.round(radius), 0, 2 * Math.PI); } -function rect(ctx, x1, y1, x2, y2) { - var correction = 0.5 * (ctx.lineWidth % 2) - x1 = x1 * globalScope.scale; - y1 = y1 * globalScope.scale; - x2 = x2 * globalScope.scale; - y2 = y2 * globalScope.scale; +export function rect(ctx, x1, y1, x2, y2) { + const correction = 0.5 * (ctx.lineWidth % 2); + x1 *= globalScope.scale; + y1 *= globalScope.scale; + x2 *= globalScope.scale; + y2 *= globalScope.scale; ctx.rect(Math.round(globalScope.ox + x1 - correction) + correction, Math.round(globalScope.oy + y1 - correction) + correction, Math.round(x2), Math.round(y2)); } -function rect2(ctx, x1, y1, x2, y2, xx, yy, dir = "RIGHT") { - var correction = 0.5 * (ctx.lineWidth % 2); +export function rect2(ctx, x1, y1, x2, y2, xx, yy, dir = 'RIGHT') { + const correction = 0.5 * (ctx.lineWidth % 2); [x1, y1] = rotate(x1, y1, dir); [x2, y2] = rotate(x2, y2, dir); - x1 = x1 * globalScope.scale; - y1 = y1 * globalScope.scale; - x2 = x2 * globalScope.scale; - y2 = y2 * globalScope.scale; + x1 *= globalScope.scale; + y1 *= globalScope.scale; + x2 *= globalScope.scale; + y2 *= globalScope.scale; xx *= globalScope.scale; yy *= globalScope.scale; ctx.rect(Math.round(globalScope.ox + xx + x1 - correction) + correction, Math.round(globalScope.oy + yy + y1 - correction) + correction, Math.round(x2), Math.round(y2)); } -function rotate(x1, y1, dir) { - if (dir == "LEFT") - return [-x1, y1]; - else if (dir == "DOWN") - return [y1, x1]; - else if (dir == "UP") - return [y1, -x1]; - else - return [x1, y1]; +export function rotate(x1, y1, dir) { + if (dir === 'LEFT') { return [-x1, y1]; } + if (dir === 'DOWN') { return [y1, x1]; } + if (dir === 'UP') { return [y1, -x1]; } + return [x1, y1]; } export function correctWidth(w) { @@ -255,14 +274,10 @@ export function correctWidth(w) { } function rotateAngle(start, stop, dir) { - if (dir == "LEFT") - return [start, stop, true]; - else if (dir == "DOWN") - return [start - Math.PI / 2, stop - Math.PI / 2, true]; - else if (dir == "UP") - return [start - Math.PI / 2, stop - Math.PI / 2, false]; - else - return [start, stop, false]; + if (dir === 'LEFT') { return [start, stop, true]; } + if (dir === 'DOWN') { return [start - Math.PI / 2, stop - Math.PI / 2, true]; } + if (dir === 'UP') { return [start - Math.PI / 2, stop - Math.PI / 2, false]; } + return [start, stop, false]; } export function drawLine(ctx, x1, y1, x2, y2, color, width) { @@ -272,13 +287,13 @@ export function drawLine(ctx, x1, y1, x2, y2, color, width) { y2 *= globalScope.scale; ctx.beginPath(); ctx.strokeStyle = color; - ctx.lineCap = "round"; - ctx.lineWidth = correctWidth(width);//*globalScope.scale; - var correction = 0.5 * (ctx.lineWidth % 2); - var hCorrection = 0; - var vCorrection = 0; - if (y1 == y2) vCorrection = correction; - if (x1 == x2) hCorrection = correction; + ctx.lineCap = 'round'; + ctx.lineWidth = correctWidth(width);//* globalScope.scale; + const correction = 0.5 * (ctx.lineWidth % 2); + let hCorrection = 0; + let vCorrection = 0; + if (y1 === y2) vCorrection = correction; + if (x1 === x2) hCorrection = correction; ctx.moveTo(Math.round(x1 + globalScope.ox + hCorrection) - hCorrection, Math.round(y1 + globalScope.oy + vCorrection) - vCorrection); ctx.lineTo(Math.round(x2 + globalScope.ox + hCorrection) - hCorrection, Math.round(y2 + globalScope.oy + vCorrection) - vCorrection); ctx.stroke(); @@ -286,14 +301,15 @@ export function drawLine(ctx, x1, y1, x2, y2, color, width) { // Checks if string color is a valid color using a hack function validColor(color) { - var $div = $("
"); - $div.css("border", "1px solid " + color); - return ($div.css("border-color") != "") + const $div = $('
'); + $div.css('border', `1px solid ${color}`); + return ($div.css('border-color') !== ''); } // Helper function to color "RED" to RGBA function colorToRGBA(color) { - var cvs, ctx; + let cvs; let + ctx; cvs = document.createElement('canvas'); cvs.height = 1; cvs.width = 1; @@ -304,8 +320,8 @@ function colorToRGBA(color) { } export function drawCircle(ctx, x1, y1, r, color) { - x1 = x1 * globalScope.scale; - y1 = y1 * globalScope.scale; + x1 *= globalScope.scale; + y1 *= globalScope.scale; ctx.beginPath(); ctx.fillStyle = color; ctx.arc(Math.round(x1 + globalScope.ox), Math.round(y1 + globalScope.oy), Math.round(r * globalScope.scale), 0, Math.PI * 2, false); @@ -317,13 +333,13 @@ export function drawCircle(ctx, x1, y1, r, color) { export function canvasMessage(ctx, str, x1, y1, fontSize = 10) { if (!str || !str.length) return; - ctx.font = Math.round(fontSize * globalScope.scale) + "px Georgia"; - ctx.textAlign = "center" - var width = ctx.measureText(str).width / globalScope.scale + 8; - var height = 13; - ctx.strokeStyle = "black"; + ctx.font = `${Math.round(fontSize * globalScope.scale)}px Georgia`; + ctx.textAlign = 'center'; + const width = ctx.measureText(str).width / globalScope.scale + 8; + const height = 13; + ctx.strokeStyle = 'black'; ctx.lineWidth = correctWidth(1); - ctx.fillStyle = "yellow"; + ctx.fillStyle = 'yellow'; ctx.save(); ctx.beginPath(); rect(ctx, x1 - width / 2, y1 - height / 2 - 3, width, height); @@ -334,93 +350,87 @@ export function canvasMessage(ctx, str, x1, y1, fontSize = 10) { ctx.stroke(); ctx.fill(); ctx.restore(); - x1 = x1 * globalScope.scale; - y1 = y1 * globalScope.scale; + x1 *= globalScope.scale; + y1 *= globalScope.scale; ctx.beginPath(); - ctx.fillStyle = "black"; + ctx.fillStyle = 'black'; ctx.fillText(str, Math.round(x1 + globalScope.ox), Math.round(y1 + globalScope.oy)); ctx.fill(); } -function fillText(ctx, str, x1, y1, fontSize = 20) { - x1 = x1 * globalScope.scale; - y1 = y1 * globalScope.scale; - ctx.font = Math.round(fontSize * globalScope.scale) + "px Georgia"; +export function fillText(ctx, str, x1, y1, fontSize = 20) { + x1 *= globalScope.scale; + y1 *= globalScope.scale; + ctx.font = `${Math.round(fontSize * globalScope.scale)}px Georgia`; ctx.fillText(str, Math.round(x1 + globalScope.ox), Math.round(y1 + globalScope.oy)); } -function fillText2(ctx, str, x1, y1, xx, yy, dir) { - - angle = { - "RIGHT": 0, - "LEFT": 0, - "DOWN": Math.PI / 2, - "UP": -Math.PI / 2, - } - x1 = x1 * globalScope.scale; - y1 = y1 * globalScope.scale; +export function fillText2(ctx, str, x1, y1, xx, yy, dir) { + const angle = { + RIGHT: 0, + LEFT: 0, + DOWN: Math.PI / 2, + UP: -Math.PI / 2, + }; + x1 *= globalScope.scale; + y1 *= globalScope.scale; [x1, y1] = rotate(x1, y1, dir); - xx = xx * globalScope.scale; - yy = yy * globalScope.scale; + xx *= globalScope.scale; + yy *= globalScope.scale; - ctx.font = Math.round(14 * globalScope.scale) + "px Georgia"; + ctx.font = `${Math.round(14 * globalScope.scale)}px Georgia`; ctx.save(); ctx.translate(Math.round(xx + x1 + globalScope.ox), Math.round(yy + y1 + globalScope.oy)); ctx.rotate(angle[dir]); - ctx.textAlign = "center"; - ctx.fillText(str, 0, Math.round(4 * globalScope.scale) * (1 - 0 * (+(dir == "DOWN")))); + ctx.textAlign = 'center'; + ctx.fillText(str, 0, Math.round(4 * globalScope.scale) * (1 - 0 * (+(dir === 'DOWN')))); ctx.restore(); - } -function fillText4(ctx, str, x1, y1, xx, yy, dir, fontSize = 14, textAlign = "center") { - angle = { - "RIGHT": 0, - "LEFT": 0, - "DOWN": Math.PI / 2, - "UP": -Math.PI / 2, - } - x1 = x1 * globalScope.scale; - y1 = y1 * globalScope.scale; +export function fillText4(ctx, str, x1, y1, xx, yy, dir, fontSize = 14, textAlign = 'center') { + const angle = { + RIGHT: 0, + LEFT: 0, + DOWN: Math.PI / 2, + UP: -Math.PI / 2, + }; + x1 *= globalScope.scale; + y1 *= globalScope.scale; [x1, y1] = rotate(x1, y1, dir); - xx = xx * globalScope.scale; - yy = yy * globalScope.scale; + xx *= globalScope.scale; + yy *= globalScope.scale; - ctx.font = Math.round(fontSize * globalScope.scale) + "px Georgia"; + ctx.font = `${Math.round(fontSize * globalScope.scale)}px Georgia`; // ctx.font = 20+"px Georgia"; ctx.textAlign = textAlign; ctx.fillText(str, xx + x1 + globalScope.ox, yy + y1 + globalScope.oy + Math.round(fontSize / 3 * globalScope.scale)); - - } -function fillText3(ctx, str, x1, y1, xx = 0, yy = 0, fontSize = 14, font = "Georgia", textAlign = "center") { - - x1 = x1 * globalScope.scale; - y1 = y1 * globalScope.scale; - xx = xx * globalScope.scale; - yy = yy * globalScope.scale; +export function fillText3(ctx, str, x1, y1, xx = 0, yy = 0, fontSize = 14, font = 'Georgia', textAlign = 'center') { + x1 *= globalScope.scale; + y1 *= globalScope.scale; + xx *= globalScope.scale; + yy *= globalScope.scale; - ctx.font = Math.round(fontSize * globalScope.scale) + "px " + font; + ctx.font = `${Math.round(fontSize * globalScope.scale)}px ${font}`; ctx.textAlign = textAlign; ctx.fillText(str, Math.round(xx + x1 + globalScope.ox), Math.round(yy + y1 + globalScope.oy)); - } export const oppositeDirection = { - "RIGHT": "LEFT", - "LEFT": "RIGHT", - "DOWN": "UP", - "UP": "DOWN", -} + RIGHT: 'LEFT', + LEFT: 'RIGHT', + DOWN: 'UP', + UP: 'DOWN', +}; export const fixDirection = { - "right": "LEFT", - "left": "RIGHT", - "down": "UP", - "up": "DOWN", - "LEFT": "LEFT", - "RIGHT": "RIGHT", - "UP": "UP", - "DOWN": "DOWN", -} + right: 'LEFT', + left: 'RIGHT', + down: 'UP', + up: 'DOWN', + LEFT: 'LEFT', + RIGHT: 'RIGHT', + UP: 'UP', + DOWN: 'DOWN', +}; diff --git a/src/circuit.js b/src/circuit.js index 5ad8755..880a2fa 100644 --- a/src/circuit.js +++ b/src/circuit.js @@ -1,178 +1,179 @@ -import CircuitElement from "./circuitElement"; -import * as metadata from './metadata.json' -import { generateId,newCircuit,showMessage } from "./utils"; -import { backgroundArea } from "./backgroundArea"; -import { plotArea } from "./plotArea"; -import { simulationArea } from "./simulationArea"; -import { dots } from "./canvasApi"; -import { update } from "./engine"; -import { setupUI } from "./ux"; -import { startListeners } from "./listeners"; -function setupEnvironment() { - - const projectId = generateId(); - const updateSimulation = true; - const DPR = window.devicePixelRatio || 1; - newCircuit("Main"); - - window.data = {} - resetup(); +/* eslint-disable import/no-cycle */ +/* eslint-disable no-bitwise */ +/* eslint-disable guard-for-in */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-restricted-globals */ +/* eslint-disable consistent-return */ +/* eslint-disable func-names */ +/* eslint-disable array-callback-return */ +/* eslint-disable no-use-before-define */ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-alert */ +import CircuitElement from './circuitElement'; +import simulationArea, { changeClockTime } from './simulationArea'; +import { + stripTags, uniq, showMessage, showError, +} from './utils'; +import { findDimensions, dots } from './canvasApi'; +import { updateRestrictedElementsList } from './restrictedElementDiv'; +import { scheduleBackup } from './data/backupCircuit'; +import { showProperties } from './ux'; +import { + scheduleUpdate, updateSimulationSet, + updateCanvasSet, updateSubcircuitSet, + forceResetNodesSet, changeLightMode, +} from './engine'; +import { toggleLayoutMode, layoutModeGet } from './layoutMode'; +import { setProjectName } from './data/save'; +import { changeClockEnable } from './sequential'; +import { changeInputSize } from './modules'; + +export const circuitProperty = { + toggleLayoutMode, + setProjectName, + changeCircuitName, + changeClockTime, + deleteCurrentCircuit, + changeClockEnable, + changeInputSize, + changeLightMode, +}; +export let scopeList = {}; +export function resetScopeList() { + scopeList = {}; } -var width -var height -export { - width, - height -} -export function hello() { - console.log("hello") -} -//to resize window and setup things -function resetup() { - - DPR = window.devicePixelRatio || 1; - if (lightMode) - DPR = 1; - width = document.getElementById("simulationArea").clientWidth * DPR; +/** + * Function used to change the current focusedCircuit + * Disables layoutMode if enabled + * Changes UI tab etc + * Sets flags to make updates, resets most of the things + * @param {string} id - identifier for circuit + * @category circuit + */ +export function switchCircuit(id) { + if (layoutModeGet()) { toggleLayoutMode(); } + + // globalScope.fixLayout(); + scheduleBackup(); + if (id === globalScope.id) return; + $(`#${globalScope.id}`).removeClass('current'); + $(`#${id}`).addClass('current'); + simulationArea.lastSelected = undefined; + simulationArea.multipleObjectSelections = []; + simulationArea.copyList = []; + globalScope = scopeList[id]; + updateSimulationSet(true); + updateSubcircuitSet(true); + forceResetNodesSet(true); + dots(false); + simulationArea.lastSelected = globalScope.root; if (!embed) { - height = (document.getElementById("simulation").clientHeight ) * DPR; - } else { - height = (document.getElementById("simulation").clientHeight) * DPR; + showProperties(simulationArea.lastSelected); } - - //setup simulationArea - backgroundArea.setup(); - if (!embed) plotArea.setup(); - simulationArea.setup(); - - // update(); - dots(); - - document.getElementById("backgroundArea").style.height = height / DPR + 100; - document.getElementById("backgroundArea").style.width = width / DPR + 100; - document.getElementById("canvasArea").style.height = height / DPR; - simulationArea.canvas.width = width; - simulationArea.canvas.height = height; - backgroundArea.canvas.width = width + 100 * DPR; - backgroundArea.canvas.height = height + 100 * DPR; - if (!embed) { - plotArea.c.width = document.getElementById("plot").clientWidth; - plotArea.c.height = document.getElementById("plot").clientHeight - } - - updateCanvas = true; - update(); // INEFFICIENT, needs to be deprecated - simulationArea.prevScale = 0; - dots(true, false); -} - -function setupElementLists() { - - $('#menu').empty(); - - window.circuitElementList = metadata.circuitElementList; - window.annotationList = metadata.annotationList; - window.inputList = metadata.inputList; - window.subCircuitInputList = metadata.subCircuitInputList; - window.moduleList = [...circuitElementList, ...annotationList] - window.updateOrder = ["wires", ...circuitElementList, "nodes", ...annotationList]; // Order of update - window.renderOrder = [...(moduleList.slice().reverse()), "wires", "allNodes"]; // Order of render + updateCanvasSet(true); + scheduleUpdate(); + // to update the restricted elements information + updateRestrictedElementsList(); +} - function createIcon(element) { - return `
- -

${element}

-
`; +/** + * Deletes the current circuit + * Ensures that at least one circuit is there + * Ensures that no circuit depends on the current circuit + * Switched to a random circuit + * @category circuit +*/ +function deleteCurrentCircuit(scopeId = globalScope.id) { + const scope = scopeList[scopeId]; + if (Object.keys(scopeList).length <= 1) { + showError('At least 2 circuits need to be there in order to delete a circuit.'); + return; } - - let elementHierarchy = metadata.elementHierarchy; - for (let category in elementHierarchy) { - let htmlIcons = ''; - - let categoryData = elementHierarchy[category]; - - for (let i = 0; i < categoryData.length; i++) { - let element = categoryData[i]; - htmlIcons += createIcon(element); + let dependencies = ''; + for (id in scopeList) { + if (id != scope.id && scopeList[id].checkDependency(scope.id)) { + if (dependencies === '') { + dependencies = scopeList[id].name; + } else { + dependencies += `, ${scopeList[id].name}`; + } } - - let accordionData = `
${category}
-
- ${htmlIcons} -
`; - - $('#menu').append(accordionData); - + } + if (dependencies) { + dependencies = `\nThe following circuits are depending on '${scope.name}': ${dependencies}\nDelete subcircuits of ${scope.name} before trying to delete ${scope.name}`; + alert(dependencies); + return; } - + const confirmation = confirm(`Are you sure want to delete: ${scope.name}\nThis cannot be undone.`); + if (confirmation) { + $(`#${scope.id}`).remove(); + delete scopeList[scope.id]; + switchCircuit(Object.keys(scopeList)[0]); + showMessage('Circuit was successfully deleted'); + } else { showMessage('Circuit was not deleted'); } } -export function setup() { - - setupElementLists(); - setupEnvironment(); - if (!embed) - setupUI(); - startListeners(); - projectName = "untitled" - // Load project data after 1 second - needs to be improved, delay needs to be eliminated - setTimeout(function () { - if (logix_project_id != 0) { - $('.loadingIcon').fadeIn(); - $.ajax({ - url: '/simulator/get_data', - type: 'POST', - beforeSend: function (xhr) { - xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content')) - }, - data: { - "id": logix_project_id - }, - success: function (response) { - data = (response); - - if (data) { - load(data); - simulationArea.changeClockTime(data["timePeriod"] || 500); - } - $('.loadingIcon').fadeOut(); - }, - failure: function () { - alert("Error: could not load "); - $('.loadingIcon').fadeOut(); - } - }); - - } - - // Restore unsaved data and save - else if (localStorage.getItem("recover_login") && userSignedIn) { - var data = JSON.parse(localStorage.getItem("recover_login")); - load(data); - localStorage.removeItem("recover"); - localStorage.removeItem("recover_login"); - save(); - } - - // Restore unsaved data which didn't get saved due to error - else if (localStorage.getItem("recover")) { - showMessage("We have detected that you did not save your last work. Don't worry we have recovered them. Access them using Project->Recover") - } - }, 1000); - +/** + * Function to create new circuit + * Function creates button in tab, creates scope and switches to this circuit + * @param {string} name - name of the new circuit + * @param {string} id - identifier for circuit + * @category circuit + */ +export function newCircuit(name, id) { + name = name || prompt('Enter circuit name:'); + name = stripTags(name); + if (!name) return; + const scope = new Scope(name); + if (id) scope.id = id; + scopeList[scope.id] = scope; + globalScope = scope; + $('.circuits').removeClass('current'); + $('#tabsBar').append(`
${name}
`); + $('.circuits').click(function () { + switchCircuit(this.id); + }); + $('.tabsCloseButton').click(function (e) { + e.stopPropagation(); + deleteCurrentCircuit(this.id); + }); + if (!embed) { + showProperties(scope.root); + } + dots(false); + return scope; +} +/** + * Used to change name of a circuit + * @param {string} name - new name + * @param {string} id - id of the circuit + * @category circuit + */ +export function changeCircuitName(name, id = globalScope.id) { + name = name || 'Untitled'; + name = stripTags(name); + $(`#${id}`).html(name); + scopeList[id].name = name; } -export class Scope { - constructor(name = "localScope", id = undefined) { +/** + * Class representing a Scope + * @class + * @param {string} name - name of the circuit + * @param {number=} id - a random id for the circuit + * @category circuit + */ +export default class Scope { + constructor(name = 'localScope', id = undefined) { this.restrictedCircuitElementsUsed = []; this.id = id || Math.floor((Math.random() * 100000000000) + 1); this.CircuitElement = []; - //root object for referring to main canvas - intermediate node uses this - this.root = new CircuitElement(0, 0, this, "RIGHT", 1); + // root object for referring to main canvas - intermediate node uses this + this.root = new CircuitElement(0, 0, this, 'RIGHT', 1); this.backups = []; this.timeStamp = new Date().getTime(); @@ -180,16 +181,16 @@ export class Scope { this.oy = 0; this.scale = DPR; this.tunnelList = {}; - this.stack = [] + this.stack = []; this.name = name; - this.pending = [] - this.nodes = []; //intermediate nodes only + this.pending = []; + this.nodes = []; // intermediate nodes only this.allNodes = []; this.wires = []; // Creating arrays for other module elements - for (var i = 0; i < moduleList.length; i++) { + for (let i = 0; i < moduleList.length; i++) { this[moduleList[i]] = []; } @@ -200,7 +201,7 @@ export class Scope { title_x: 50, title_y: 13, titleEnabled: true, - } + }; // FOR SOME UNKNOWN REASON, MAKING THE COPY OF THE LIST COMMON @@ -211,90 +212,93 @@ export class Scope { // this.renderObjectOrder = [ ...(moduleList.slice().reverse()), "wires", "allNodes"]; } - // Resets all nodes recursively + /** + * Resets all nodes recursively + */ reset() { - for (var i = 0; i < this.allNodes.length; i++) - this.allNodes[i].reset(); - for (var i = 0; i < this.Splitter.length; i++) { + for (let i = 0; i < this.allNodes.length; i++) { this.allNodes[i].reset(); } + for (let i = 0; i < this.Splitter.length; i++) { this.Splitter[i].reset(); } - for (var i = 0; i < this.SubCircuit.length; i++) { + for (let i = 0; i < this.SubCircuit.length; i++) { this.SubCircuit[i].reset(); } - } - // Adds all inputs to simulationQueue + /** + * Adds all inputs to simulationQueue + */ addInputs() { - for (var i = 0; i < inputList.length; i++) { - for (var j = 0; j < this[inputList[i]].length; j++) { + for (let i = 0; i < inputList.length; i++) { + for (let j = 0; j < this[inputList[i]].length; j++) { simulationArea.simulationQueue.add(this[inputList[i]][j], 0); } } - for (let j = 0; j < this.SubCircuit.length; j++) - this.SubCircuit[j].addInputs(); - + for (let i = 0; i < this.SubCircuit.length; i++) { this.SubCircuit[i].addInputs(); } } - // Ticks clocks recursively -- needs to be deprecated and synchronize all clocks with a global clock + /** + * Ticks clocks recursively -- needs to be deprecated and synchronize all clocks with a global clock + */ clockTick() { - for (var i = 0; i < this.Clock.length; i++) - this.Clock[i].toggleState(); //tick clock! - for (var i = 0; i < this.SubCircuit.length; i++) - this.SubCircuit[i].localScope.clockTick(); //tick clock! + for (let i = 0; i < this.Clock.length; i++) { this.Clock[i].toggleState(); } // tick clock! + for (let i = 0; i < this.SubCircuit.length; i++) { this.SubCircuit[i].localScope.clockTick(); } // tick clock! } - // Checks if this circuit contains directly or indirectly scope with id - // Recursive nature + /** + * Checks if this circuit contains directly or indirectly scope with id + * Recursive nature + */ checkDependency(id) { - if (id == this.id) return true; - for (var i = 0; i < this.SubCircuit.length; i++) - if (this.SubCircuit[i].id == id) return true; + if (id === this.id) return true; + for (let i = 0; i < this.SubCircuit.length; i++) { if (this.SubCircuit[i].id === id) return true; } - for (var i = 0; i < this.SubCircuit.length; i++) - if (scopeList[this.SubCircuit[i].id].checkDependency(id)) return true; + for (let i = 0; i < this.SubCircuit.length; i++) { if (scopeList[this.SubCircuit[i].id].checkDependency(id)) return true; } - return false + return false; } - // Get dependency list - list of all circuits, this circuit depends on + /** + * Get dependency list - list of all circuits, this circuit depends on + */ getDependencies() { - var list = [] - for (var i = 0; i < this.SubCircuit.length; i++) { + const list = []; + for (let i = 0; i < this.SubCircuit.length; i++) { list.push(this.SubCircuit[i].id); list.extend(scopeList[this.SubCircuit[i].id].getDependencies()); } return uniq(list); } - // helper function to reduce layout size + /** + * helper function to reduce layout size + */ fixLayout() { - var max_y = 20; - for (var i = 0; i < this.Input.length; i++) - max_y = Math.max(this.Input[i].layoutProperties.y, max_y) - for (var i = 0; i < this.Output.length; i++) - max_y = Math.max(this.Output[i].layoutProperties.y, max_y) - if (max_y != this.layout.height) - this.layout.height = max_y + 10; + let maxY = 20; + for (let i = 0; i < this.Input.length; i++) { maxY = Math.max(this.Input[i].layoutProperties.y, maxY); } + for (let i = 0; i < this.Output.length; i++) { maxY = Math.max(this.Output[i].layoutProperties.y, maxY); } + if (maxY !== this.layout.height) { this.layout.height = maxY + 10; } } - // Function which centers the circuit to the correct zoom level + + /** + * Function which centers the circuit to the correct zoom level + */ centerFocus(zoomIn = true) { - if (layoutMode) return; + if (layoutModeGet()) return; findDimensions(this); - var minX = simulationArea.minWidth || 0; - var minY = simulationArea.minHeight || 0; - var maxX = simulationArea.maxWidth || 0; - var maxY = simulationArea.maxHeight || 0; + const minX = simulationArea.minWidth || 0; + const minY = simulationArea.minHeight || 0; + const maxX = simulationArea.maxWidth || 0; + const maxY = simulationArea.maxHeight || 0; - var reqWidth = maxX - minX + 150; - var reqHeight = maxY - minY + 150; + const reqWidth = maxX - minX + 150; + const reqHeight = maxY - minY + 150; - this.scale = Math.min(width / reqWidth, height / reqHeight) + this.scale = Math.min(width / reqWidth, height / reqHeight); - if (!zoomIn) - this.scale = Math.min(this.scale, DPR); + if (!zoomIn) { this.scale = Math.min(this.scale, DPR); } this.scale = Math.max(this.scale, DPR / 10); this.ox = (-minX) * this.scale + (width - (maxX - minX) * this.scale) / 2; diff --git a/src/circuitElement.js b/src/circuitElement.js index cbca7dc..8c6ac50 100644 --- a/src/circuitElement.js +++ b/src/circuitElement.js @@ -1,51 +1,53 @@ -import { scheduleUpdate } from "./engine"; -import { width,height } from "./circuit"; -import { simulationArea } from "./simulationArea"; -const oppositeDirection = { - "RIGHT": "LEFT", - "LEFT": "RIGHT", - "DOWN": "UP", - "UP": "DOWN", -} +/* eslint-disable no-multi-assign */ +/* eslint-disable no-bitwise */ +import { scheduleUpdate } from './engine'; +import simulationArea from './simulationArea'; +import { + fixDirection, fillText, correctWidth, rect2, oppositeDirection, +} from './canvasApi'; + +/** + * Base class for circuit elements. + * @param {number} x - x coordinate of the element + * @param {number} y - y coordinate of the element + * @param {Scope} scope - The circuit on which circuit element is being drawn + * @param {string} dir - The direction of circuit element + * @param {number} bitWidth - the number of bits per node. + * @category circuitElement + */ export default class CircuitElement { constructor(x, y, scope, dir, bitWidth) { // Data member initializations - this.objectType = this.constructor.name; this.x = x; this.y = y; - this.hover = false; - if (this.x == undefined || this.y == undefined) { + if (this.x === undefined || this.y === undefined) { this.x = simulationArea.mouseX; this.y = simulationArea.mouseY; this.newElement = true; this.hover = true; } - this.deleteNodesWhenDeleted = true; // FOR NOW - TO CHECK LATER - - this.nodeList = [] + this.nodeList = []; this.clicked = false; this.oldx = x; this.oldy = y; - /** - The following attributes help in setting the touch area bound. They are the distances from the center. - Note they are all positive distances from center. They will automatically be rotated when direction is changed. - To stop the rotation when direction is changed, check overrideDirectionRotation attribute. - **/ + // The following attributes help in setting the touch area bound. They are the distances from the center. + // Note they are all positive distances from center. They will automatically be rotated when direction is changed. + // To stop the rotation when direction is changed, check overrideDirectionRotation attribute. this.leftDimensionX = 10; this.rightDimensionX = 10; this.upDimensionY = 10; this.downDimensionY = 10; this.rectangleObject = true; - this.label = ""; + this.label = ''; this.scope = scope; - this.scope[this.objectType].push(this); + this.baseSetup(); - this.bitWidth = bitWidth || parseInt(prompt("Enter bitWidth"), 10) || 1; + this.bitWidth = bitWidth || parseInt(prompt('Enter bitWidth'), 10) || 1; this.direction = dir; this.directionFixed = false; this.labelDirectionFixed = false; @@ -59,49 +61,81 @@ export default class CircuitElement { inQueue: false, time: undefined, index: undefined, - } - + }; } + /** + * Function to flip bits + * @param {number} val - the value of flipped bits + * @returns {number} - The number of flipped bits + */ flipBits(val) { return ((~val >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); } + /** + * Function to get absolute value of x coordinate of the element + * @param {number} x - value of x coordinate of the element + * @return {number} - absolute value of x + */ absX() { return this.x; } + /** + * Function to get absolute value of y coordinate of the element + * @param {number} y - value of y coordinate of the element + * @return {number} - absolute value of y + */ absY() { return this.y; } + /** + * adds the element to scopeList + */ + baseSetup() { + this.scope[this.objectType].push(this); + } + + /** + * Function to copy the circuit element obj to a new circuit element + * @param {CircuitElement} obj - element to be copied from + */ copyFrom(obj) { - var properties = ["label", "labelDirection"]; - for (var i = 0; i < properties.length; i++) { - if (obj[properties[i]] !== undefined) - this[properties[i]] = obj[properties[i]]; + const properties = ['label', 'labelDirection']; + for (let i = 0; i < properties.length; i++) { + if (obj[properties[i]] !== undefined) { this[properties[i]] = obj[properties[i]]; } } } - /* Methods to be Implemented for derivedClass - saveObject(); //To generate JSON-safe data that can be loaded - customDraw(); //This is to draw the custom design of the circuit(Optional) - resolve(); // To execute digital logic(Optional) - override isResolvable(); // custom logic for checking if module is ready - override newDirection(dir) //To implement custom direction logic(Optional) - newOrientation(dir) //To implement custom orientation logic(Optional) - */ + /** Methods to be Implemented for derivedClass + * saveObject(); //To generate JSON-safe data that can be loaded + * customDraw(); //This is to draw the custom design of the circuit(Optional) + * resolve(); // To execute digital logic(Optional) + * override isResolvable(); // custom logic for checking if module is ready + * override newDirection(dir) //To implement custom direction logic(Optional) + * newOrientation(dir) //To implement custom orientation logic(Optional) + */ // Method definitions + /** + * Function to update the scope when a new element is added. + * @param {Scope} scope - the circuit in which we add element + */ updateScope(scope) { this.scope = scope; - for (var i = 0; i < this.nodeList.length; i++) - this.nodeList[i].scope = scope; + for (let i = 0; i < this.nodeList.length; i++) { this.nodeList[i].scope = scope; } } + /** + * To generate JSON-safe data that can be loaded + * @memberof CircuitElement + * @return {JSON} - the data to be saved + */ saveObject() { - var data = { + const data = { x: this.x, y: this.y, objectType: this.objectType, @@ -109,69 +143,100 @@ export default class CircuitElement { direction: this.direction, labelDirection: this.labelDirection, propagationDelay: this.propagationDelay, - customData: this.customSave() - } + customData: this.customSave(), + }; return data; - } + /** + * Always overriden + * @memberof CircuitElement + * @return {JSON} - the data to be saved + */ + // eslint-disable-next-line class-methods-use-this customSave() { return { values: {}, nodes: {}, constructorParamaters: [], - } + }; } + /** + * check hover over the element + * @return {boolean} + */ checkHover() { - if (simulationArea.mouseDown) return; - for (var i = 0; i < this.nodeList.length; i++) { + for (let i = 0; i < this.nodeList.length; i++) { this.nodeList[i].checkHover(); } if (!simulationArea.mouseDown) { - if (simulationArea.hover == this) { + if (simulationArea.hover === this) { this.hover = this.isHover(); if (!this.hover) simulationArea.hover = undefined; } else if (!simulationArea.hover) { this.hover = this.isHover(); if (this.hover) simulationArea.hover = this; } else { - this.hover = false + this.hover = false; } } } - //This sets the width and height of the element if its rectangular - // and the reference point is at the center of the object. - //width and height define the X and Y distance from the center. - //Effectively HALF the actual width and height. - // NOT OVERRIDABLE + + /** + * This sets the width and height of the element if its rectangular + * and the reference point is at the center of the object. + * width and height define the X and Y distance from the center. + * Effectively HALF the actual width and height. + * NOT OVERRIDABLE + * @param {number} w - width + * @param {number} h - height + */ setDimensions(width, height) { this.leftDimensionX = this.rightDimensionX = width; this.downDimensionY = this.upDimensionY = height; } + /** + * @memberof CircuitElement + * @param {number} w -width + */ setWidth(width) { this.leftDimensionX = this.rightDimensionX = width; } + /** + * @param {number} h -height + */ setHeight(height) { this.downDimensionY = this.upDimensionY = height; } + /** + * Helper Function to drag element to a new position + */ startDragging() { this.oldx = this.x; this.oldy = this.y; } + /** + * Helper Function to drag element to a new position + * @memberof CircuitElement + */ drag() { this.x = this.oldx + simulationArea.mouseX - simulationArea.mouseDownX; this.y = this.oldy + simulationArea.mouseY - simulationArea.mouseDownY; } + /** + * The update method is used to change the parameters of the object on mouse click and hover. + * Return Value: true if state has changed else false + * NOT OVERRIDABLE + */ update() { - let update = false; update |= this.newElement; @@ -187,16 +252,14 @@ export default class CircuitElement { if (simulationArea.mouseDown) { this.newElement = false; simulationArea.lastSelected = this; - console.log(this) - } else return; + } else return update; } for (let i = 0; i < this.nodeList.length; i++) { update |= this.nodeList[i].update(); } - if (!simulationArea.hover || simulationArea.hover == this) - this.hover = this.isHover(); + if (!simulationArea.hover || simulationArea.hover === this) { this.hover = this.isHover(); } if (!simulationArea.mouseDown) this.hover = false; @@ -204,26 +267,24 @@ export default class CircuitElement { if ((this.clicked || !simulationArea.hover) && this.isHover()) { this.hover = true; simulationArea.hover = this; - } else if (!simulationArea.mouseDown && this.hover && this.isHover() == false) { + } else if (!simulationArea.mouseDown && this.hover && this.isHover() === false) { if (this.hover) simulationArea.hover = undefined; this.hover = false; } if (simulationArea.mouseDown && (this.clicked)) { - this.drag(); if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) { - for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) { + for (let i = 0; i < simulationArea.multipleObjectSelections.length; i++) { simulationArea.multipleObjectSelections[i].drag(); } } update |= true; } else if (simulationArea.mouseDown && !simulationArea.selected) { - this.startDragging(); if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) { - for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) { + for (let i = 0; i < simulationArea.multipleObjectSelections.length; i++) { simulationArea.multipleObjectSelections[i].startDragging(); } } @@ -256,34 +317,38 @@ export default class CircuitElement { return update; } + /** + * Helper Function to correct the direction of element + */ fixDirection() { this.direction = fixDirection[this.direction] || this.direction; this.labelDirection = fixDirection[this.labelDirection] || this.labelDirection; } - // The isHover method is used to check if the mouse is hovering over the object. - // Return Value: true if mouse is hovering over object else false - // NOT OVERRIDABLE + /** + * The isHover method is used to check if the mouse is hovering over the object. + * Return Value: true if mouse is hovering over object else false + * NOT OVERRIDABLE + */ isHover() { + const mX = simulationArea.mouseXf - this.x; + const mY = this.y - simulationArea.mouseYf; - var mX = simulationArea.mouseXf - this.x; - var mY = this.y - simulationArea.mouseYf; - - var rX = this.rightDimensionX; - var lX = this.leftDimensionX; - var uY = this.upDimensionY; - var dY = this.downDimensionY; + let rX = this.rightDimensionX; + let lX = this.leftDimensionX; + let uY = this.upDimensionY; + let dY = this.downDimensionY; if (!this.directionFixed && !this.overrideDirectionRotation) { - if (this.direction == "LEFT") { + if (this.direction === 'LEFT') { lX = this.rightDimensionX; - rX = this.leftDimensionX - } else if (this.direction == "DOWN") { + rX = this.leftDimensionX; + } else if (this.direction === 'DOWN') { lX = this.downDimensionY; rX = this.upDimensionY; uY = this.leftDimensionX; dY = this.rightDimensionX; - } else if (this.direction == "UP") { + } else if (this.direction === 'UP') { lX = this.downDimensionY; rX = this.upDimensionY; dY = this.leftDimensionX; @@ -294,14 +359,21 @@ export default class CircuitElement { return -lX <= mX && mX <= rX && -dY <= mY && mY <= uY; } + /** + * Helper Function to set label of an element. + * @memberof CircuitElement + * @param {string} label - the label for element + */ setLabel(label) { - this.label = label || "" + this.label = label || ''; } - //Method that draws the outline of the module and calls draw function on module Nodes. - //NOT OVERRIDABLE + /** + * Method that draws the outline of the module and calls draw function on module Nodes. + * NOT OVERRIDABLE + */ draw() { - var ctx = simulationArea.context; + const ctx = simulationArea.context; this.checkHover(); @@ -309,30 +381,30 @@ export default class CircuitElement { // Draws rectangle and highlights if (this.rectangleObject) { - ctx.strokeStyle = "black"; - ctx.fillStyle = "white"; + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; ctx.lineWidth = correctWidth(3); ctx.beginPath(); - rect2(ctx, -this.leftDimensionX, -this.upDimensionY, this.leftDimensionX + this.rightDimensionX, this.upDimensionY + this.downDimensionY, this.x, this.y, [this.direction, "RIGHT"][+this.directionFixed]); - if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; + rect2(ctx, -this.leftDimensionX, -this.upDimensionY, this.leftDimensionX + this.rightDimensionX, this.upDimensionY + this.downDimensionY, this.x, this.y, [this.direction, 'RIGHT'][+this.directionFixed]); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; ctx.fill(); ctx.stroke(); } - if (this.label != "") { - var rX = this.rightDimensionX; - var lX = this.leftDimensionX; - var uY = this.upDimensionY; - var dY = this.downDimensionY; + if (this.label !== '') { + let rX = this.rightDimensionX; + let lX = this.leftDimensionX; + let uY = this.upDimensionY; + let dY = this.downDimensionY; if (!this.directionFixed) { - if (this.direction == "LEFT") { + if (this.direction === 'LEFT') { lX = this.rightDimensionX; - rX = this.leftDimensionX - } else if (this.direction == "DOWN") { + rX = this.leftDimensionX; + } else if (this.direction === 'DOWN') { lX = this.downDimensionY; rX = this.upDimensionY; uY = this.leftDimensionX; dY = this.rightDimensionX; - } else if (this.direction == "UP") { + } else if (this.direction === 'UP') { lX = this.downDimensionY; rX = this.upDimensionY; dY = this.leftDimensionX; @@ -340,172 +412,199 @@ export default class CircuitElement { } } - if (this.labelDirection == "LEFT") { + if (this.labelDirection === 'LEFT') { ctx.beginPath(); - ctx.textAlign = "right"; - ctx.fillStyle = "black"; + ctx.textAlign = 'right'; + ctx.fillStyle = 'black'; fillText(ctx, this.label, this.x - lX - 10, this.y + 5, 14); ctx.fill(); - } else if (this.labelDirection == "RIGHT") { + } else if (this.labelDirection === 'RIGHT') { ctx.beginPath(); - ctx.textAlign = "left"; - ctx.fillStyle = "black"; + ctx.textAlign = 'left'; + ctx.fillStyle = 'black'; fillText(ctx, this.label, this.x + rX + 10, this.y + 5, 14); ctx.fill(); - } else if (this.labelDirection == "UP") { + } else if (this.labelDirection === 'UP') { ctx.beginPath(); - ctx.textAlign = "center"; - ctx.fillStyle = "black"; + ctx.textAlign = 'center'; + ctx.fillStyle = 'black'; fillText(ctx, this.label, this.x, this.y + 5 - uY - 10, 14); ctx.fill(); - } else if (this.labelDirection == "DOWN") { + } else if (this.labelDirection === 'DOWN') { ctx.beginPath(); - ctx.textAlign = "center"; - ctx.fillStyle = "black"; + ctx.textAlign = 'center'; + ctx.fillStyle = 'black'; fillText(ctx, this.label, this.x, this.y + 5 + dY + 10, 14); ctx.fill(); } } // calls the custom circuit design - if (this.customDraw) - this.customDraw(); + if (this.customDraw) { this.customDraw(); } - //draws nodes - Moved to renderCanvas - // for (var i = 0; i < this.nodeList.length; i++) + // draws nodes - Moved to renderCanvas + // for (let i = 0; i < this.nodeList.length; i++) // this.nodeList[i].draw(); } - //method to delete object - //OVERRIDE WITH CAUTION + // method to delete object + // OVERRIDE WITH CAUTION delete() { simulationArea.lastSelected = undefined; this.scope[this.objectType].clean(this); // CHECK IF THIS IS VALID - if (this.deleteNodesWhenDeleted) - this.deleteNodes(); - else - for (var i = 0; i < this.nodeList.length; i++) - if (this.nodeList[i].connections.length) - this.nodeList[i].converToIntermediate(); - else - this.nodeList[i].delete(); + if (this.deleteNodesWhenDeleted) { this.deleteNodes(); } else { + for (let i = 0; i < this.nodeList.length; i++) { + if (this.nodeList[i].connections.length) { this.nodeList[i].converToIntermediate(); } else { this.nodeList[i].delete(); } + } + } this.deleted = true; } + /** + * method to delete object + * OVERRIDE WITH CAUTION + * @memberof CircuitElement + */ cleanDelete() { this.deleteNodesWhenDeleted = true; this.delete(); } + /** + * Helper Function to delete the element and all the node attached to it. + */ deleteNodes() { - for (var i = 0; i < this.nodeList.length; i++) - this.nodeList[i].delete(); + for (let i = 0; i < this.nodeList.length; i++) { this.nodeList[i].delete(); } } - //method to change direction - //OVERRIDE WITH CAUTION + /** + * method to change direction + * OVERRIDE WITH CAUTION + * @param {string} dir - new direction + */ newDirection(dir) { - if (this.direction == dir) return; + if (this.direction === dir) return; // Leave this for now if (this.directionFixed && this.orientationFixed) return; - else if (this.directionFixed) { + if (this.directionFixed) { this.newOrientation(dir); return; // Should it return ? } - // if (obj.direction == undefined) return; + // if (obj.direction === undefined) return; this.direction = dir; - for (var i = 0; i < this.nodeList.length; i++) { + for (let i = 0; i < this.nodeList.length; i++) { this.nodeList[i].refresh(); } - } + /** + * Helper Function to change label direction of the element. + * @memberof CircuitElement + * @param {string} dir - new direction + */ newLabelDirection(dir) { this.labelDirection = dir; } - //Method to check if object can be resolved - //OVERRIDE if necessary + /** + * Method to check if object can be resolved + * OVERRIDE if necessary + * @return {boolean} + */ isResolvable() { if (this.alwaysResolve) return true; - for (var i = 0; i < this.nodeList.length; i++) - if (this.nodeList[i].type == 0 && this.nodeList[i].value == undefined) return false; + for (let i = 0; i < this.nodeList.length; i++) { if (this.nodeList[i].type === 0 && this.nodeList[i].value === undefined) return false; } return true; } - //Method to change object Bitwidth - //OVERRIDE if necessary + + /** + * Method to change object Bitwidth + * OVERRIDE if necessary + * @param {number} bitWidth - new bitwidth + */ newBitWidth(bitWidth) { if (this.fixedBitWidth) return; - if (this.bitWidth == undefined) return; + if (this.bitWidth === undefined) return; if (this.bitWidth < 1) return; this.bitWidth = bitWidth; - for (var i = 0; i < this.nodeList.length; i++) - this.nodeList[i].bitWidth = bitWidth; + for (let i = 0; i < this.nodeList.length; i++) { this.nodeList[i].bitWidth = bitWidth; } } - //Method to change object delay - //OVERRIDE if necessary + /** + * Method to change object delay + * OVERRIDE if necessary + * @param {number} delay - new delay + */ changePropagationDelay(delay) { if (this.propagationDelayFixed) return; - if (delay == undefined) return; - if (delay == "") return; - delay = parseInt(delay, 10) - if (delay < 0) return; - this.propagationDelay = delay; + if (delay === undefined) return; + if (delay === '') return; + const tmpDelay = parseInt(delay, 10); + if (tmpDelay < 0) return; + this.propagationDelay = tmpDelay; } - //Dummy resolve function - //OVERRIDE if necessary + /** + * Dummy resolve function + * OVERRIDE if necessary + */ resolve() { } + /** + * Helper Function to process verilog + */ processVerilog() { - var output_count = 0; - for (var i = 0; i < this.nodeList.length; i++) { - if (this.nodeList[i].type == NODE_OUTPUT) { - this.nodeList[i].verilogLabel = this.nodeList[i].verilogLabel || (this.verilogLabel + "_" + (verilog.fixName(this.nodeList[i].label) || ("out_" + output_count))); - if (this.objectType != "Input" && this.nodeList[i].connections.length > 0) { - if (this.scope.verilogWireList[this.bitWidth] != undefined) { - if (!this.scope.verilogWireList[this.bitWidth].contains(this.nodeList[i].verilogLabel)) - this.scope.verilogWireList[this.bitWidth].push(this.nodeList[i].verilogLabel); - } else - this.scope.verilogWireList[this.bitWidth] = [this.nodeList[i].verilogLabel]; + let outputCount = 0; + for (let i = 0; i < this.nodeList.length; i++) { + if (this.nodeList[i].type === NODE_OUTPUT) { + this.nodeList[i].verilogLabel = this.nodeList[i].verilogLabel || (`${this.verilogLabel}_${verilog.fixName(this.nodeList[i].label) || (`out_${outputCount}`)}`); + if (this.objectType !== 'Input' && this.nodeList[i].connections.length > 0) { + if (this.scope.verilogWireList[this.bitWidth] !== undefined) { + if (!this.scope.verilogWireList[this.bitWidth].contains(this.nodeList[i].verilogLabel)) { this.scope.verilogWireList[this.bitWidth].push(this.nodeList[i].verilogLabel); } + } else { this.scope.verilogWireList[this.bitWidth] = [this.nodeList[i].verilogLabel]; } } this.scope.stack.push(this.nodeList[i]); - output_count++; + outputCount++; } } } + /** + * Helper Function to check if verilog resolvable + * @return {boolean} + */ isVerilogResolvable() { - - var backupValues = [] - for (var i = 0; i < this.nodeList.length; i++) { + const backupValues = []; + for (let i = 0; i < this.nodeList.length; i++) { backupValues.push(this.nodeList[i].value); this.nodeList[i].value = undefined; } - for (var i = 0; i < this.nodeList.length; i++) { + for (let i = 0; i < this.nodeList.length; i++) { if (this.nodeList[i].verilogLabel) { this.nodeList[i].value = 1; } } - var res = this.isResolvable(); + const res = this.isResolvable(); - for (var i = 0; i < this.nodeList.length; i++) { + for (let i = 0; i < this.nodeList.length; i++) { this.nodeList[i].value = backupValues[i]; } return res; } + /** + * Helper Function to remove proporgation. + */ removePropagation() { - for (var i = 0; i < this.nodeList.length; i++) { - if (this.nodeList[i].type == NODE_OUTPUT) { + for (let i = 0; i < this.nodeList.length; i++) { + if (this.nodeList[i].type === NODE_OUTPUT) { if (this.nodeList[i].value !== undefined) { this.nodeList[i].value = undefined; simulationArea.simulationQueue.add(this.nodeList[i]); @@ -514,42 +613,40 @@ export default class CircuitElement { } } + /** + * Helper Function to name the verilog. + * @return {string} + */ verilogName() { return this.verilogType || this.objectType; } + /** + * Helper Function to generate verilog + * @return {JSON} + */ generateVerilog() { - - var inputs = []; - var outputs = []; + const inputs = []; + const outputs = []; - for (var i = 0; i < this.nodeList.length; i++) { - if (this.nodeList[i].type == NODE_INPUT) { + for (let i = 0; i < this.nodeList.length; i++) { + if (this.nodeList[i].type === NODE_INPUT) { inputs.push(this.nodeList[i]); } else { outputs.push(this.nodeList[i]); } } - var list = outputs.concat(inputs); - var res = this.verilogName() + " " + this.verilogLabel + " (" + list.map(function (x) { - return x.verilogLabel - }).join(",") + ");"; + const list = outputs.concat(inputs); + const res = `${this.verilogName()} ${this.verilogLabel} (${list.map((x) => x.verilogLabel).join(',')});`; return res; } } -// CircuitElement.prototype.alwaysResolve = false -// CircuitElement.prototype.propagationDelay = 10 - -// The update method is used to change the parameters of the object on mouse click and hover. -// Return Value: true if state has changed else false -// NOT OVERRIDABLE - -// When true this.isHover() will not rotate bounds. To be used when bounds are set manually. -// CircuitElement.prototype.overrideDirectionRotation = false; - -// CircuitElement.prototype.propagationDelayFixed = false; -// +CircuitElement.prototype.alwaysResolve = false; +CircuitElement.prototype.propagationDelay = 10; +CircuitElement.prototype.tooltip = undefined; +CircuitElement.prototype.propagationDelayFixed = false; +CircuitElement.prototype.objectType = 'CircuitElement'; diff --git a/src/combinationalAnalysis.js b/src/combinationalAnalysis.js new file mode 100755 index 0000000..f02a305 --- /dev/null +++ b/src/combinationalAnalysis.js @@ -0,0 +1,279 @@ +/* eslint-disable import/no-cycle */ +/* eslint-disable guard-for-in */ +/* eslint-disable no-restricted-syntax */ +import Node from './node'; +import { scheduleBackup } from './data/backupCircuit'; +import BooleanMinimize from './quinMcCluskey'; +import Input from './modules/Input'; +import Output from './modules/Output'; +import AndGate from './modules/AndGate'; +import OrGate from './modules/OrGate'; +import NotGate from './modules/NotGate'; + +const inputSample = 5; +const dataSample = [['01---', '11110', '01---', '00000'], ['01110', '1-1-1', '----0'], ['01---', '11110', '01110', '1-1-1', '0---0'], ['----1']]; + +const sampleInputListNames = ['A', 'B']; +const sampleOutputListNames = ['X']; + +/** + * The prompt for combinational analysis + * @param {Scope=} - the circuit in which we want combinational analysis + * @category combinationalAnalysis + */ +export function createCombinationalAnalysisPrompt(scope = globalScope) { + // console.log("Ya"); + scheduleBackup(); + $('#combinationalAnalysis').empty(); + $('#combinationalAnalysis').append("

Enter Input names separated by commas:

"); + $('#combinationalAnalysis').append("

Enter Output names separated by commas:

"); + $('#combinationalAnalysis').append("

Do you need a decimal column?

"); + $('#combinationalAnalysis').dialog({ + width: 'auto', + buttons: [ + { + text: 'Next', + click() { + let inputList = $('#inputNameList').val().split(','); + let outputList = $('#outputNameList').val().split(','); + inputList = inputList.map((x) => x.trim()); + inputList = inputList.filter((e) => e); + outputList = outputList.map((x) => x.trim()); + outputList = outputList.filter((e) => e); + if (inputList.length > 0 && outputList.length > 0) { + $(this).dialog('close'); + createBooleanPrompt(inputList, outputList, scope); + } else { + alert('Enter Input / Output Variable(s) !'); + } + }, + }, + ], + }); +} +/** + * This funciton hashes the output array and makes required JSON using + * a BooleanMinimize class defined in Quin_Mcluskey.js let s which will + * be output table is also initialied here + * @param {Array} inputListNames - labels of input nodes + * @param {Array} outputListNames - labels of output nodes + * @param {Scope=} scope - h circuit + * @category combinationalAnalysis + */ +function createBooleanPrompt(inputListNames, outputListNames, scope = globalScope) { + inputListNames = inputListNames || (prompt('Enter inputs separated by commas').split(',')); + outputListNames = outputListNames || (prompt('Enter outputs separated by commas').split(',')); + const outputListNamesInteger = []; + for (let i = 0; i < outputListNames.length; i++) { outputListNamesInteger[i] = 7 * i + 13; }// assigning an integer to the value, 7*i + 13 is random + + let s = ''; + s += ''; + s += ''; + if ($('#decimalColumnBox').is(':checked')) { s += ''; } + for (let i = 0; i < inputListNames.length; i++) { s += ``; } + for (let i = 0; i < outputListNames.length; i++) { s += ``; } + s += ''; + + const matrix = []; + for (let i = 0; i < inputListNames.length; i++) { + matrix[i] = new Array((1 << inputListNames.length)); + } + + for (let i = 0; i < inputListNames.length; i++) { + for (let j = 0; j < (1 << inputListNames.length); j++) { + matrix[i][j] = (+((j & (1 << (inputListNames.length - i - 1))) != 0)); + } + } + + for (let j = 0; j < (1 << inputListNames.length); j++) { + s += ''; + if ($('#decimalColumnBox').is(':checked')) { s += ``; } + for (let i = 0; i < inputListNames.length; i++) { + s += ``; + } + for (let i = 0; i < outputListNamesInteger.length; i++) { + s += `'; + // using hash values as they'll be used in the generateBooleanTableData function + } + s += ''; + } + s += ''; + s += '
' + 'dec' + '${inputListNames[i]}${outputListNames[i]}
${j}${matrix[i][j]}` + 'x' + '
'; + // console.log(s) + $('#combinationalAnalysis').empty(); + $('#combinationalAnalysis').append(s); + $('#combinationalAnalysis').dialog({ + width: 'auto', + buttons: [ + { + text: 'Generate Circuit', + click() { + $(this).dialog('close'); + const data = generateBooleanTableData(outputListNamesInteger); + // passing the hash values to avoid spaces being passed which is causing a problem + const minmizedCircuit = []; + for (const output in data) { + const temp = new BooleanMinimize( + inputListNames.length, + data[output][1].map(Number), + data[output].x.map(Number), + ); + minmizedCircuit.push(temp.result); + } + // //console.log(dataSample); + drawCombinationalAnalysis(minmizedCircuit, inputListNames, outputListNames, scope); + }, + }, + { + text: 'Print Truth Table', + click() { + const sTable = document.getElementById('combinationalAnalysis').innerHTML; + const style = ''; + const win = window.open('', '', 'height=700,width=700'); + win.document.write(''); + win.document.write('Boolean Logic Table'); + win.document.write(style); + win.document.write(''); + win.document.write(''); + win.document.write(`
${sTable}
`); + win.document.write(''); + win.document.close(); + win.print(); + }, + }, + ], + }); + + $('.output').click(function () { + let v = $(this).html(); + if (v == 0)v = $(this).html(1); + else if (v == 1)v = $(this).html('x'); + else if (v == 'x')v = $(this).html(0); + }); +} + +function generateBooleanTableData(outputListNames) { + const data = {}; + for (let i = 0; i < outputListNames.length; i++) { + data[outputListNames[i]] = { + x: [], + 1: [], + 0: [], + }; + const rows = $(`.${outputListNames[i]}`); + for (let j = 0; j < rows.length; j++) { + // console.log($rows[j].innerHTML) + data[outputListNames[i]][rows[j].innerHTML].push(rows[j].id); + } + } + // console.log(data); + return data; +} + +function drawCombinationalAnalysis(combinationalData, inputList, outputListNames, scope = globalScope) { + // console.log(combinationalData); + const inputCount = inputList.length; + let maxTerms = 0; + for (let i = 0; i < combinationalData.length; i++) { maxTerms = Math.max(maxTerms, combinationalData[i].length); } + + const startPosX = 200; + const startPosY = 200; + + let currentPosY = 300; + const andPosX = startPosX + inputCount * 40 + 40; + const orPosX = andPosX + Math.floor(maxTerms / 2) * 10 + 80; + const outputPosX = orPosX + 60; + const inputObjects = []; + + const logixNodes = []; + + for (let i = 0; i < inputCount; i++) { + inputObjects.push(new Input(startPosX + i * 40, startPosY, scope, 'DOWN', 1)); + inputObjects[i].setLabel(inputList[i]); + inputObjects[i].newLabelDirection('UP'); + const v1 = new Node(startPosX + i * 40, startPosY + 20, 2, scope.root); + inputObjects[i].output1.connect(v1); + const v2 = new Node(startPosX + i * 40 + 20, startPosY + 20, 2, scope.root); + v1.connect(v2); + const notG = new NotGate(startPosX + i * 40 + 20, startPosY + 40, scope, 'DOWN', 1); + notG.inp1.connect(v2); + logixNodes.push(v1); + logixNodes.push(notG.output1); + } + + function countTerm(s) { + let c = 0; + for (let i = 0; i < s.length; i++) { if (s[i] !== '-')c++; } + return c; + } + + for (let i = 0; i < combinationalData.length; i++) { + // //console.log(combinationalData[i]); + const andGateNodes = []; + for (let j = 0; j < combinationalData[i].length; j++) { + const c = countTerm(combinationalData[i][j]); + if (c > 1) { + const andGate = new AndGate(andPosX, currentPosY, scope, 'RIGHT', c, 1); + andGateNodes.push(andGate.output1); + let misses = 0; + for (let k = 0; k < combinationalData[i][j].length; k++) { + if (combinationalData[i][j][k] == '-') { misses++; continue; } + const index = 2 * k + (combinationalData[i][j][k] == 0); + // console.log(index); + // console.log(andGate); + const v = new Node(logixNodes[index].absX(), andGate.inp[k - misses].absY(), 2, scope.root); + logixNodes[index].connect(v); + logixNodes[index] = v; + v.connect(andGate.inp[k - misses]); + } + } else { + for (let k = 0; k < combinationalData[i][j].length; k++) { + if (combinationalData[i][j][k] == '-') continue; + const index = 2 * k + (combinationalData[i][j][k] == 0); + const andGateSubstituteNode = new Node(andPosX, currentPosY, 2, scope.root); + const v = new Node(logixNodes[index].absX(), andGateSubstituteNode.absY(), 2, scope.root); + logixNodes[index].connect(v); + logixNodes[index] = v; + v.connect(andGateSubstituteNode); + andGateNodes.push(andGateSubstituteNode); + } + } + currentPosY += c * 10 + 30; + } + + const andGateCount = andGateNodes.length; + const midWay = Math.floor(andGateCount / 2); + let orGatePosY = (andGateNodes[midWay].absY() + andGateNodes[Math.floor((andGateCount - 1) / 2)].absY()) / 2; + if (orGatePosY % 10 == 5) { orGatePosY += 5; } // To make or gate fall in grid + if (andGateCount > 1) { + const o = new OrGate(orPosX, orGatePosY, scope, 'RIGHT', andGateCount, 1); + if (andGateCount % 2 == 1)andGateNodes[midWay].connect(o.inp[midWay]); + for (let j = 0; j < midWay; j++) { + let v = new Node(andPosX + 30 + (midWay - j) * 10, andGateNodes[j].absY(), 2, scope.root); + v.connect(andGateNodes[j]); + let v2 = new Node(andPosX + 30 + (midWay - j) * 10, o.inp[j].absY(), 2, scope.root); + v2.connect(v); + o.inp[j].connect(v2); + + v = new Node(andPosX + 30 + (midWay - j) * 10, andGateNodes[andGateCount - j - 1].absY(), 2, scope.root); + v.connect(andGateNodes[andGateCount - j - 1]); + v2 = new Node(andPosX + 30 + (midWay - j) * 10, o.inp[andGateCount - j - 1].absY(), 2, scope.root); + v2.connect(v); + o.inp[andGateCount - j - 1].connect(v2); + } + const out = new Output(outputPosX, o.y, scope, 'LEFT', 1); + out.inp1.connect(o.output1); + } else { + const out = new Output(outputPosX, andGateNodes[0].absY(), scope, 'LEFT', 1); + out.inp1.connect(andGateNodes[0]); + } + out.setLabel(outputListNames[i]); + out.newLabelDirection('RIGHT'); + } + for (let i = 0; i < logixNodes.length; i++) { + if (logixNodes[i].absY() != currentPosY) { + const v = new Node(logixNodes[i].absX(), currentPosY, 2, scope.root); + logixNodes[i].connect(v); + } + } +} diff --git a/src/data.js b/src/data.js new file mode 100755 index 0000000..8432361 --- /dev/null +++ b/src/data.js @@ -0,0 +1,25 @@ +import { createSubCircuitPrompt } from './subcircuit'; +import save from './data/save'; +import load from './data/load'; +import createSaveAsImgPrompt from './data/saveImage'; +import { + clearProject, newProject, saveOffline, openOffline, recoverProject, +} from './data/project'; +import { newCircuit } from './circuit'; +import { createCombinationalAnalysisPrompt } from './combinationalAnalysis'; +import { createBitConverter } from './dec-bin-hex'; + +const logixFunction = {}; +logixFunction.save = save; +logixFunction.load = load; +logixFunction.createSaveAsImgPrompt = createSaveAsImgPrompt; +logixFunction.clearProject = clearProject; +logixFunction.newProject = newProject; +logixFunction.saveOffline = saveOffline; +logixFunction.newCircuit = newCircuit; +logixFunction.createOpenLocalPrompt = openOffline; +logixFunction.recoverProject = recoverProject; +logixFunction.createSubCircuitPrompt = createSubCircuitPrompt; +logixFunction.createCombinationalAnalysisPrompt = createCombinationalAnalysisPrompt; +logixFunction.bitconverter = createBitConverter; +export default logixFunction; diff --git a/src/data/backupCircuit.js b/src/data/backupCircuit.js new file mode 100755 index 0000000..f53a370 --- /dev/null +++ b/src/data/backupCircuit.js @@ -0,0 +1,63 @@ +import { projectSavedSet } from './project'; +/* eslint-disable no-param-reassign */ +function extract(obj) { + return obj.saveObject(); +} + +// Check if there is anything to backup - to be deprecated +/** + * Check if backup is available + * @param {Scope} scope + * @return {boolean} + * @category data + */ +export function checkIfBackup(scope) { + for (let i = 0; i < updateOrder.length; i++) { if (scope[updateOrder[i]].length) return true; } + return false; +} + +export function backUp(scope = globalScope) { + // Disconnection of subcircuits are needed because these are the connections between nodes + // in current scope and those in the subcircuit's scope + for (let i = 0; i < scope.SubCircuit.length; i++) { scope.SubCircuit[i].removeConnections(); } + + const data = {}; + + // Storing layout + data.layout = scope.layout; + + // Storing all nodes + data.allNodes = scope.allNodes.map(extract); + + // Storing other details + data.id = scope.id; + data.name = scope.name; + + // Storing details of all module objects + for (let i = 0; i < moduleList.length; i++) { + if (scope[moduleList[i]].length) { data[moduleList[i]] = scope[moduleList[i]].map(extract); } + } + + // Adding restricted circuit elements used in the save data + data.restrictedCircuitElementsUsed = scope.restrictedCircuitElementsUsed; + + // Storing intermediate nodes (nodes in wires) + data.nodes = []; + for (let i = 0; i < scope.nodes.length; i++) { data.nodes.push(scope.allNodes.indexOf(scope.nodes[i])); } + + // Restoring the connections + for (let i = 0; i < scope.SubCircuit.length; i++) { scope.SubCircuit[i].makeConnections(); } + + return data; +} + +export function scheduleBackup(scope = globalScope) { + const backup = JSON.stringify(backUp(scope)); + if (scope.backups.length === 0 || scope.backups[scope.backups.length - 1] !== backup) { + scope.backups.push(backup); + scope.timeStamp = new Date().getTime(); + projectSavedSet(false); + } + + return backup; +} diff --git a/src/data/load.js b/src/data/load.js new file mode 100755 index 0000000..3527c88 --- /dev/null +++ b/src/data/load.js @@ -0,0 +1,210 @@ +import { resetScopeList, newCircuit, switchCircuit } from '../circuit'; +import { setProjectName } from './save'; +import { + scheduleUpdate, update, updateSimulationSet, updateCanvasSet, gridUpdateSet, +} from '../engine'; +import { updateRestrictedElementsInScope } from '../restrictedElementDiv'; +import simulationArea from '../simulationArea'; + +import { loadSubCircuit } from '../subcircuit'; +import { scheduleBackup } from './backupCircuit'; +import { showProperties } from '../ux'; +import { constructNodeConnections, loadNode, replace } from '../node'; +import { generateId } from '../utils'; +import modules from '../modules'; +import { oppositeDirection } from '../canvasApi'; + +/** + * Backward compatibility - needs to be deprecated + * @param {CircuitElement} obj - the object to be rectified + * @category data + */ +function rectifyObjectType(obj) { + const rectify = { + FlipFlop: 'DflipFlop', + Ram: 'Rom', + }; + return rectify[obj] || obj; +} + +/** + * Function to load CircuitElements + * @param {JSON} data - JSOn data + * @param {Scope} scope - circuit in which we want to load modules + * @category data + */ +function loadModule(data, scope) { + // Create circuit element + const obj = new modules[rectifyObjectType(data.objectType)](data.x, data.y, scope, ...data.customData.constructorParamaters || []); + // Sets directions + obj.label = data.label; + obj.labelDirection = data.labelDirection || oppositeDirection[fixDirection[obj.direction]]; + + // Sets delay + obj.propagationDelay = data.propagationDelay || obj.propagationDelay; + obj.fixDirection(); + + // Restore other values + if (data.customData.values) { + for (const prop in data.customData.values) { + obj[prop] = data.customData.values[prop]; + } + } + + // Replace new nodes with the correct old nodes (with connections) + if (data.customData.nodes) { + for (const node in data.customData.nodes) { + const n = data.customData.nodes[node]; + if (n instanceof Array) { + for (let i = 0; i < n.length; i++) { + obj[node][i] = replace(obj[node][i], n[i]); + } + } else { + obj[node] = replace(obj[node], n); + } + } + } +} + +/** + * This function shouldn't ideally exist. But temporary fix + * for some issues while loading nodes. + * @category data + */ +function removeBugNodes(scope = globalScope) { + let x = scope.allNodes.length; + for (let i = 0; i < x; i++) { + if (scope.allNodes[i].type !== 2 && scope.allNodes[i].parent.objectType === 'CircuitElement') { scope.allNodes[i].delete(); } + if (scope.allNodes.length !== x) { + i = 0; + x = scope.allNodes.length; + } + } +} + +/** + * Function to load a full circuit + * @param {Scope} scope + * @param {JSON} data + * @category data + */ +export function loadScope(scope, data) { + const ML = moduleList.slice(); // Module List copy + scope.restrictedCircuitElementsUsed = data.restrictedCircuitElementsUsed; + + // Load all nodes + data.allNodes.map((x) => loadNode(x, scope)); + + // Make all connections + for (let i = 0; i < data.allNodes.length; i++) { constructNodeConnections(scope.allNodes[i], data.allNodes[i]); } + // Load all modules + for (let i = 0; i < ML.length; i++) { + if (data[ML[i]]) { + if (ML[i] === 'SubCircuit') { + // Load subcircuits differently + for (let j = 0; j < data[ML[i]].length; j++) { loadSubCircuit(data[ML[i]][j], scope); } + } else { + // Load everything else similarly + for (let j = 0; j < data[ML[i]].length; j++) { + loadModule(data[ML[i]][j], scope); + } + } + } + } + // Update wires according + scope.wires.map((x) => { + x.updateData(scope); + }); + removeBugNodes(scope); // To be deprecated + // If layout exists, then restore + if (data.layout) { + scope.layout = data.layout; + } else { + // Else generate new layout according to how it would have been otherwise (backward compatibility) + scope.layout = {}; + scope.layout.width = 100; + scope.layout.height = Math.max(scope.Input.length, scope.Output.length) * 20 + 20; + scope.layout.title_x = 50; + scope.layout.title_y = 13; + for (let i = 0; i < scope.Input.length; i++) { + scope.Input[i].layoutProperties = { + x: 0, + y: scope.layout.height / 2 - scope.Input.length * 10 + 20 * i + 10, + id: generateId(), + }; + } + for (let i = 0; i < scope.Output.length; i++) { + scope.Output[i].layoutProperties = { + x: scope.layout.width, + y: scope.layout.height / 2 - scope.Output.length * 10 + 20 * i + 10, + id: generateId(), + }; + } + } + // Backward compatibility + if (scope.layout.titleEnabled === undefined) { scope.layout.titleEnabled = true; } +} + + +// Function to load project from data +/** + * loads a saved project + * @param {JSON} data - the json data of the + * @category data + * @exports load + */ +export default function load(data) { + // If project is new and no data is there, then just set project name + if (!data) { + setProjectName(projectName); + return; + } + + const { projectId } = data; + let projectName = data.name; + + if (data.name === 'Untitled') { projectName = undefined; } else { setProjectName(data.name); } + + globalScope = undefined; + resetScopeList(); // Remove default scope + $('.circuits').remove(); // Delete default scope + + // Load all circuits according to the dependency order + for (let i = 0; i < data.scopes.length; i++) { + // Create new circuit + const scope = newCircuit(data.scopes[i].name || 'Untitled', data.scopes[i].id); + + // Load circuit data + loadScope(scope, data.scopes[i]); + + // Focus circuit + globalScope = scope; + + // Center circuit + if (embed) { globalScope.centerFocus(true); } else { globalScope.centerFocus(false); } + + // update and backup circuit once + update(globalScope, true); + + // Updating restricted element list initially on loading + updateRestrictedElementsInScope(); + + scheduleBackup(); + } + + // Restore clock + simulationArea.changeClockTime(data.timePeriod || 500); + simulationArea.clockEnabled = data.clockEnabled === undefined ? true : data.clockEnabled; + + + if (!embed) { showProperties(simulationArea.lastSelected); } + + // Switch to last focussedCircuit + if (data.focussedCircuit) switchCircuit(data.focussedCircuit); + + + updateSimulationSet(true); + updateCanvasSet(true); + gridUpdateSet(true); + scheduleUpdate(); +} diff --git a/src/data/project.js b/src/data/project.js new file mode 100755 index 0000000..5a612d1 --- /dev/null +++ b/src/data/project.js @@ -0,0 +1,138 @@ +/* eslint-disable guard-for-in */ +/* eslint-disable no-bitwise */ +/* eslint-disable import/no-cycle */ +/* eslint-disable no-restricted-globals */ +/* eslint-disable no-alert */ +import { resetScopeList, scopeList, newCircuit } from '../circuit'; +import { showMessage, showError } from '../utils'; +import { checkIfBackup } from './backupCircuit'; +import { generateSaveData } from './save'; +import load from './load'; + +/** + * Helper function to recover unsaved data + * @category data + */ +export function recoverProject() { + if (localStorage.getItem('recover')) { + const data = JSON.parse(localStorage.getItem('recover')); + if (confirm(`Would you like to recover: ${data.name}`)) { + load(data); + } + localStorage.removeItem('recover'); + } else { + showError('No recover project found'); + } +} + + +/** + * Prompt to restore from localStorage + * @category data + */ +export function openOffline() { + $('#openProjectDialog').empty(); + const projectList = JSON.parse(localStorage.getItem('projectList')); + let flag = true; + for (id in projectList) { + flag = false; + $('#openProjectDialog').append(``); + } + if (flag) $('#openProjectDialog').append('

Looks like no circuit has been saved yet. Create a new one and save it!

'); + $('#openProjectDialog').dialog({ + width: 'auto', + buttons: !flag ? [{ + text: 'Open Project', + click() { + if (!$('input[name=projectId]:checked').val()) return; + load(JSON.parse(localStorage.getItem($('input[name=projectId]:checked').val()))); + $(this).dialog('close'); + }, + }] : [], + + }); +} +/** + * Flag for project saved or not + * @type {boolean} + * @category data + */ +let projectSaved = true; +export function projectSavedSet(param) { + projectSaved = param; +} + + +/** + * Helper function to store to localStorage -- needs to be deprecated/removed + * @category data + */ +export function saveOffline() { + const data = generateSaveData(); + localStorage.setItem(projectId, data); + const temp = JSON.parse(localStorage.getItem('projectList')) || {}; + temp[projectId] = projectName; + localStorage.setItem('projectList', JSON.stringify(temp)); + showMessage(`We have saved your project: ${projectName} in your browser's localStorage`); +} + +/** + * Checks if any circuit has unsaved data + * @category data + */ +function checkToSave() { + let saveFlag = false; + // eslint-disable-next-line no-restricted-syntax + for (id in scopeList) { + saveFlag |= checkIfBackup(scopeList[id]); + } + return saveFlag; +} + +/** + * Prompt user to save data if unsaved + * @category data + */ +window.onbeforeunload = function () { + if (projectSaved || embed) return; + + if (!checkToSave()) return; + + alert('You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?'); + const data = generateSaveData('Untitled'); + localStorage.setItem('recover', data); + // eslint-disable-next-line consistent-return + return 'Are u sure u want to leave? Any unsaved changes may not be recoverable'; +}; + + +/** + * Function to clear project + * @category data + */ +export function clearProject() { + if (confirm('Would you like to clear the project?')) { + globalScope = undefined; + resetScopeList(); + $('.circuits').remove(); + newCircuit('main'); + showMessage('Your project is as good as new!'); + } +} + +/** + Function used to start a new project while prompting confirmation from the user + * @param {boolean} verify - flag to verify a new project + * @category data + */ +export function newProject(verify) { + if (verify || projectSaved || !checkToSave() || confirm('What you like to start a new project? Any unsaved changes will be lost.')) { + clearProject(); + localStorage.removeItem('recover'); + window.location = '/simulator'; + + projectName = undefined; + projectId = generateId(); + showMessage('New Project has been created!'); + } +} diff --git a/src/data/save.js b/src/data/save.js new file mode 100755 index 0000000..1dbe602 --- /dev/null +++ b/src/data/save.js @@ -0,0 +1,332 @@ +import { scopeList } from '../circuit'; +import { resetup } from '../setup'; +import { update } from '../engine'; +import { stripTags, showMessage } from '../utils'; +import { backUp } from './backupCircuit'; +import simulationArea from '../simulationArea'; +import backgroundArea from '../backgroundArea'; +import { findDimensions } from '../canvasApi'; +import { projectSavedSet } from './project'; + +/** + * Function to set the name of project. + * @param {string} name - name for project + * @category data + */ +export function setProjectName(name) { + name = stripTags(name); + const projectName = name; + $('#projectName').html(name); +} + +/** + * Helper function to save canvas as image based on image type + * @param {string} name -name of the circuit + * @param {string} imgType - image type ex: png,jpg etc. + * @category data + */ +function downloadAsImg(name, imgType) { + const gh = simulationArea.canvas.toDataURL(`image/${imgType}`); + const anchor = document.createElement('a'); + anchor.href = gh; + anchor.download = `${name}.${imgType}`; + anchor.click(); +} + +/** + * Generates JSON of the entire project + * @param {string} name - the name of project + * @return {JSON} + * @category data + */ +export function generateSaveData(name) { + data = {}; + + // Prompts for name, defaults to Untitled + name = projectName || name || prompt('Enter Project Name:') || 'Untitled'; + data.name = stripTags(name); + projectName = data.name; + setProjectName(projectName); + + // Save project details + data.timePeriod = simulationArea.timePeriod; + data.clockEnabled = simulationArea.clockEnabled; + data.projectId = projectId; + data.focussedCircuit = globalScope.id; + + // Project Circuits, each scope is one circuit + data.scopes = []; + const dependencyList = {}; + const completed = {}; + + // Getting list of dependencies for each circuit + for (id in scopeList) { dependencyList[id] = scopeList[id].getDependencies(); } + + // Helper function to save Scope + // Recursively saves inner subcircuits first, before saving parent circuits + function saveScope(id) { + if (completed[id]) return; + + for (let i = 0; i < dependencyList[id].length; i++) { + // Save inner subcircuits + saveScope(dependencyList[id][i]); + } + + completed[id] = true; + + update(scopeList[id], true); // For any pending integrity checks on subcircuits + + data.scopes.push(backUp(scopeList[id])); + } + + // Save all circuits + for (id in scopeList) { saveScope(id); } + + // convert to text + data = JSON.stringify(data); + return data; +} + +// Helper function to download text +function download(filename, text) { + const pom = document.createElement('a'); + pom.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`); + pom.setAttribute('download', filename); + + + if (document.createEvent) { + const event = document.createEvent('MouseEvents'); + event.initEvent('click', true, true); + pom.dispatchEvent(event); + } else { + pom.click(); + } +} + +/** + * Function to generate image for the circuit + * @param {string} imgType - ex: png,jpg etc. + * @param {string} view - view type ex: full + * @param {boolean} transparent - tranparent bg or not + * @param {number} resolution - resolution of the image + * @param {boolean=} down - will download if true + * @category data + */ +export function generateImage(imgType, view, transparent, resolution, down = true) { + // Backup all data + const backUpOx = globalScope.ox; + const backUpOy = globalScope.oy; + const backUpWidth = width; + const backUpHeight = height; + const backUpScale = globalScope.scale; + const backUpContextBackground = backgroundArea.context; + const backUpContextSimulation = simulationArea.context; + + backgroundArea.context = simulationArea.context; + + globalScope.ox *= 1 / backUpScale; + globalScope.oy *= 1 / backUpScale; + + // If SVG, create SVG context - using canvas2svg here + if (imgType === 'svg') { + simulationArea.context = new C2S(width, height); + resolution = 1; + } else if (imgType !== 'png') { + transparent = false; + } + + globalScope.scale = resolution; + + const scope = globalScope; + + // Focus circuit + const flag = 1; + if (flag) { + if (view === 'full') { + findDimensions(); + const minX = simulationArea.minWidth; + const minY = simulationArea.minHeight; + const maxX = simulationArea.maxWidth; + const maxY = simulationArea.maxHeight; + width = (maxX - minX + 100) * resolution; + height = (maxY - minY + 100) * resolution; + + globalScope.ox = (-minX + 50) * resolution; + globalScope.oy = (-minY + 50) * resolution; + } else { + globalScope.ox *= resolution; + globalScope.oy *= resolution; + width = (width * resolution) / backUpScale; + height = (height * resolution) / backUpScale; + } + } + + globalScope.ox = Math.round(globalScope.ox); + globalScope.oy = Math.round(globalScope.oy); + + simulationArea.canvas.width = width; + simulationArea.canvas.height = height; + backgroundArea.canvas.width = width; + backgroundArea.canvas.height = height; + + + backgroundArea.context = simulationArea.context; + + simulationArea.clear(); + + // Background + if (!transparent) { + simulationArea.context.fillStyle = 'white'; + simulationArea.context.rect(0, 0, width, height); + simulationArea.context.fill(); + } + + // Draw circuits, why is it updateOrder and not renderOrder? + for (let i = 0; i < renderOrder.length; i++) { + for (let j = 0; j < scope[renderOrder[i]].length; j++) { scope[renderOrder[i]][j].draw(); } + } + + let returnData; + // If circuit is to be downloaded, download, other wise return dataURL + if (down) { + if (imgType === 'svg') { + const mySerializedSVG = simulationArea.context.getSerializedSvg(); // true here, if you need to convert named to numbered entities. + download(`${globalScope.name}.svg`, mySerializedSVG); + } else { + downloadAsImg(globalScope.name, imgType); + } + } else { + returnData = simulationArea.canvas.toDataURL(`image/${imgType}`); + } + + // Restore everything + width = backUpWidth; + height = backUpHeight; + simulationArea.canvas.width = width; + simulationArea.canvas.height = height; + backgroundArea.canvas.width = width; + backgroundArea.canvas.height = height; + globalScope.scale = backUpScale; + backgroundArea.context = backUpContextBackground; + simulationArea.context = backUpContextSimulation; + globalScope.ox = backUpOx; + globalScope.oy = backUpOy; + + resetup(); + + if (!down) return returnData; +} + + +/** + * Function that is used to save image for display in the website + * @return {JSON} + * @category data + */ +function generateImageForOnline() { + simulationArea.lastSelected = undefined; // Unselect any selections + + // Fix aspect ratio to 1.6 + if (width > height * 1.6) { + height = width / 1.6; + } else { + width = height * 1.6; + } + + // Center circuits + globalScope.centerFocus(); + + // Ensure image is approximately 700 x 440 + const resolution = Math.min(700 / (simulationArea.maxWidth - simulationArea.minWidth), 440 / (simulationArea.maxHeight - simulationArea.minHeight)); + + data = generateImage('jpeg', 'current', false, resolution, false); + + // Restores Focus + globalScope.centerFocus(false); + return data; +} +/** + * Function called when you save acircuit online + * @category data + * @exports save + */ +export default function save() { + projectSavedSet(true); + + $('.loadingIcon').fadeIn(); + const data = generateSaveData(); + + if (!userSignedIn) { + // user not signed in, save locally temporarily and force user to sign in + localStorage.setItem('recover_login', data); + // Asking user whether they want to login. + if (confirm('You have to login to save the project, you will be redirected to the login page.')) window.location.href = '/users/sign_in'; + else $('.loadingIcon').fadeOut(); + // eslint-disable-next-line camelcase + } else if (logix_project_id === 0) { + // Create new project - this part needs to be improved and optimised + const form = $('
', { + action: '/simulator/create_data', + method: 'post', + }); + form.append( + $('', { + type: 'hidden', + name: 'authenticity_token', + value: $('meta[name="csrf-token"]').attr('content'), + }), + ); + form.append( + $('', { + type: 'text', + name: 'data', + value: data, + }), + ); + form.append( + $('', { + type: 'text', + name: 'image', + value: generateImageForOnline(), + }), + ); + + form.append( + $('', { + type: 'text', + name: 'name', + value: projectName, + }), + ); + + $('body').append(form); + form.submit(); + } else { + // updates project - this part needs to be improved and optimised + $.ajax({ + url: '/simulator/update_data', + type: 'POST', + beforeSend(xhr) { + xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content')); + }, + data: { + data, + id: logix_project_id, + image: generateImageForOnline(), + name: projectName, + }, + success(response) { + showMessage(`We have saved your project: ${projectName} in our servers.`); + $('.loadingIcon').fadeOut(); + localStorage.removeItem('recover'); + }, + failure(err) { + showMessage("There was an error, we couldn't save to our servers"); + $('.loadingIcon').fadeOut(); + }, + }); + } + + // Restore everything + resetup(); +} diff --git a/src/data/saveImage.js b/src/data/saveImage.js new file mode 100755 index 0000000..9e58736 --- /dev/null +++ b/src/data/saveImage.js @@ -0,0 +1,44 @@ +/** + * Helper function to show prompt to save image + * Options - resolution, image type, view + * @param {Scope=} scope - useless though + * @category data + */ +import { generateImage } from './save'; + +/** + * Function called to generate a prompt to save an image + * @category data + * @param {Scope=} - circuit whose image we want + * @exports createSaveAsImgPrompt + */ +export default function createSaveAsImgPrompt(scope = globalScope) { + $('#saveImageDialog').dialog({ + width: 'auto', + buttons: [{ + text: 'Render Circuit Image', + click() { + generateImage($('input[name=imgType]:checked').val(), $('input[name=view]:checked').val(), $('input[name=transparent]:checked').val(), $('input[name=resolution]:checked').val()); + $(this).dialog('close'); + }, + }], + + }); + $('input[name=imgType]').change(() => { + $('input[name=resolution]').prop('disabled', false); + $('input[name=transparent]').prop('disabled', false); + const imgType = $('input[name=imgType]:checked').val(); + if (imgType === 'svg') { + $('input[name=resolution][value=1]').click(); + $('input[name=view][value="full"]').click(); + $('input[name=resolution]').prop('disabled', true); + $('input[name=view]').prop('disabled', true); + } else if (imgType !== 'png') { + $('input[name=transparent]').attr('checked', false); + $('input[name=transparent]').prop('disabled', true); + $('input[name=view]').prop('disabled', false); + } else { + $('input[name=view]').prop('disabled', false); + } + }); +} diff --git a/src/data/undo.js b/src/data/undo.js new file mode 100755 index 0000000..44aa8e1 --- /dev/null +++ b/src/data/undo.js @@ -0,0 +1,44 @@ +/* eslint-disable import/no-cycle */ +/** + * Function to restore copy from backup + * @param {Scope=} scope - The circuit on which undo is called + * @category data + */ +import { layoutModeGet } from '../layoutMode'; +import Scope, { scopeList } from '../circuit'; +import { loadScope } from './load'; +import { updateRestrictedElementsInScope } from '../restrictedElementDiv'; +import { forceResetNodesSet } from '../engine'; + +/** + * Function called to generate a prompt to save an image + * @param {Scope=} - the circuit in which we want to call undo + * @category data + * @exports undo + */ +export default function undo(scope = globalScope) { + if (layoutModeGet()) return; + if (scope.backups.length === 0) return; + const backupOx = globalScope.ox; + const backupOy = globalScope.oy; + const backupScale = globalScope.scale; + globalScope.ox = 0; + globalScope.oy = 0; + const tempScope = new Scope(scope.name); + loading = true; + loadScope(tempScope, JSON.parse(scope.backups.pop())); + tempScope.backups = scope.backups; + tempScope.id = scope.id; + tempScope.name = scope.name; + scopeList[scope.id] = tempScope; + globalScope = tempScope; + globalScope.ox = backupOx; + globalScope.oy = backupOy; + globalScope.scale = backupScale; + loading = false; + forceResetNodesSet(true); + + // Updated restricted elements + updateRestrictedElementsInScope(); +} +// for html file diff --git a/src/dec-bin-hex.js b/src/dec-bin-hex.js new file mode 100644 index 0000000..8c9f5af --- /dev/null +++ b/src/dec-bin-hex.js @@ -0,0 +1,58 @@ +/** + * creates a dialog for inter converting values in different bases. + */ +export function createBitConverter() { + $('#bitconverterprompt').append(` +


+


+


+


+ `); + $('#bitconverterprompt').dialog({ + buttons: [ + { + text: 'Reset', + click() { + $('#decimalInput').val('0'); + $('#binaryInput').val('0'); + $('#octalInput').val('0'); + $('#hexInput').val('0'); + }, + }, + ], + }); + + $('#decimalInput').on('keyup', () => { + const x = parseInt($('#decimalInput').val(), 10); + setBaseValues(x); + }); + + $('#binaryInput').on('keyup', () => { + const x = parseInt($('#binaryInput').val(), 2); + setBaseValues(x); + }); + + $('#hexInput').on('keyup', () => { + const x = parseInt($('#hexInput').val(), 16); + setBaseValues(x); + }); + + $('#octalInput').on('keyup', () => { + const x = parseInt($('#octalInput').val(), 8); + setBaseValues(x); + }); +} +// convertors +const convertors = { + dec2bin: (x) => `0b${x.toString(2)}`, + dec2hex: (x) => `0x${x.toString(16)}`, + dec2octal: (x) => `0${x.toString(8)}`, +}; + +function setBaseValues(x) { + if (isNaN(x)) return; + $('#binaryInput').val(convertors.dec2bin(x)); + $('#octalInput').val(convertors.dec2octal(x)); + $('#hexInput').val(convertors.dec2hex(x)); + $('#decimalInput').val(x); +} diff --git a/src/embed.js b/src/embed.js new file mode 100644 index 0000000..19d3a31 --- /dev/null +++ b/src/embed.js @@ -0,0 +1,73 @@ +/* eslint-disable import/no-cycle */ +// Helper functions for when circuit is embedded +import { scopeList, circuitProperty } from './circuit'; +import simulationArea from './simulationArea'; +import { + scheduleUpdate, wireToBeCheckedSet, updateCanvasSet, gridUpdateSet, +} from './engine'; +import { prevPropertyObjGet, prevPropertyObjSet } from './ux'; + + +// circuitProperty.toggleFullScreen = toggleFullScreen; +$(document).ready(() => { + if (embed) { + // Clock features + $('#clockProperty').append("
"); + $('#clockProperty').append(`Time:
`); + $('#clockProperty').append(`Clock:
`); + + // Following codes need to be removed + $('.objectPropertyAttributeEmbed').on('change keyup paste click', function () { + scheduleUpdate(); + updateCanvasSet(true); + wireToBeCheckedSet(1); + if (simulationArea.lastSelected && simulationArea.lastSelected[this.name]) { prevPropertyObjSet(simulationArea.lastSelected[this.name](this.value)) || prevPropertyObjGet(); } else { circuitProperty[this.name](this.value); } + }); + + // Following codes need to be removed + $('.objectPropertyAttributeEmbedChecked').on('change keyup paste click', function () { + scheduleUpdate(); + updateCanvasSet(true); + wireToBeCheckedSet(1); + if (simulationArea.lastSelected && simulationArea.lastSelected[this.name]) { prevPropertyObjSet(simulationArea.lastSelected[this.name](this.value)) || prevPropertyObjGet(); } else { circuitProperty[this.name](this.checked); } + }); + } +}); +// Full screen toggle helper function +function toggleFullScreen(value) { + if (!getfullscreenelement()) { + GoInFullscreen(document.documentElement); + } else { + GoOutFullscreen(); + } +} +// Center focus accordingly +function exitHandler() { + setTimeout(() => { + Object.keys(scopeList).forEach((id) => { + scopeList[id].centerFocus(true); + }); + gridUpdateSet(true); + scheduleUpdate(); + }, 100); +} + +function GoInFullscreen(element) { + if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } else if (element.webkitRequestFullscreen) { element.webkitRequestFullscreen(); } else if (element.msRequestFullscreen) { element.msRequestFullscreen(); } +} + +function GoOutFullscreen() { + if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } +} + +function getfullscreenelement() { + return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; +} + +// Full screen Listeners +if (document.addEventListener) { + document.addEventListener('webkitfullscreenchange', exitHandler, false); + document.addEventListener('mozfullscreenchange', exitHandler, false); + document.addEventListener('fullscreenchange', exitHandler, false); + document.addEventListener('MSFullscreenChange', exitHandler, false); +} diff --git a/src/embedListeners.js b/src/embedListeners.js new file mode 100644 index 0000000..9faff14 --- /dev/null +++ b/src/embedListeners.js @@ -0,0 +1,245 @@ +/* eslint-disable import/no-cycle */ +// Listeners when circuit is embedded +// Refer listeners.js +import simulationArea from './simulationArea'; +import { + scheduleUpdate, update, updateSelectionsAndPane, + wireToBeCheckedSet, updatePositionSet, updateSimulationSet, + updateCanvasSet, gridUpdateSet, errorDetectedSet, +} from './engine'; +import { changeScale } from './canvasApi'; +import { copy, paste } from './events'; + +const unit = 10; +export default function startListeners() { + window.addEventListener('keyup', (e) => { + scheduleUpdate(1); + if (e.keyCode == 16) { + simulationArea.shiftDown = false; + } + if (e.key == 'Meta' || e.key == 'Control') { + simulationArea.controlDown = false; + } + }); + + document.getElementById('simulationArea').addEventListener('mousedown', (e) => { + errorDetectedSet(false); + updateSimulationSet(true); + updatePositionSet(true); + updateCanvasSet(true); + + simulationArea.lastSelected = undefined; + simulationArea.selected = false; + simulationArea.hover = undefined; + const rect = simulationArea.canvas.getBoundingClientRect(); + simulationArea.mouseDownRawX = (e.clientX - rect.left) * DPR; + simulationArea.mouseDownRawY = (e.clientY - rect.top) * DPR; + simulationArea.mouseDownX = Math.round(((simulationArea.mouseDownRawX - globalScope.ox) / globalScope.scale) / unit) * unit; + simulationArea.mouseDownY = Math.round(((simulationArea.mouseDownRawY - globalScope.oy) / globalScope.scale) / unit) * unit; + simulationArea.mouseDown = true; + simulationArea.oldx = globalScope.ox; + simulationArea.oldy = globalScope.oy; + + + e.preventDefault(); + scheduleUpdate(1); + }); + + document.getElementById('simulationArea').addEventListener('mousemove', () => { + const ele = document.getElementById('elementName'); + if (globalScope && simulationArea && simulationArea.objectList) { + let { objectList } = simulationArea; + objectList = objectList.filter((val) => val !== 'wires'); + + for (let i = 0; i < objectList.length; i++) { + for (let j = 0; j < globalScope[objectList[i]].length; j++) { + if (globalScope[objectList[i]][j].isHover()) { + ele.style.display = 'block'; + if (objectList[i] === 'SubCircuit') { + ele.innerHTML = `Subcircuit: ${globalScope.SubCircuit[j].data.name}`; + } else { + ele.innerHTML = `CircuitElement: ${objectList[i]}`; + } + return; + } + } + } + } + + ele.style.display = 'none'; + document.getElementById('elementName').innerHTML = ''; + }); + + window.addEventListener('mousemove', (e) => { + const rect = simulationArea.canvas.getBoundingClientRect(); + simulationArea.mouseRawX = (e.clientX - rect.left) * DPR; + simulationArea.mouseRawY = (e.clientY - rect.top) * DPR; + simulationArea.mouseXf = (simulationArea.mouseRawX - globalScope.ox) / globalScope.scale; + simulationArea.mouseYf = (simulationArea.mouseRawY - globalScope.oy) / globalScope.scale; + simulationArea.mouseX = Math.round(simulationArea.mouseXf / unit) * unit; + simulationArea.mouseY = Math.round(simulationArea.mouseYf / unit) * unit; + + updateCanvasSet(true); + if (simulationArea.lastSelected == globalScope.root) { + updateCanvasSet(true); + let fn; + fn = function () { + updateSelectionsAndPane(); + }; + scheduleUpdate(0, 20, fn); + } else { + scheduleUpdate(0, 200); + } + }); + window.addEventListener('keydown', (e) => { + errorDetectedSet(false); + updateSimulationSet(true); + updatePositionSet(true); + + // zoom in (+) + if (e.key == 'Meta' || e.key == 'Control') { + simulationArea.controlDown = true; + } + + if (simulationArea.controlDown && (e.keyCode == 187 || e.KeyCode == 171)) { + e.preventDefault(); + if (globalScope.scale < 4 * DPR) { changeScale(0.1 * DPR); } + } + + // zoom out (-) + if (simulationArea.controlDown && (e.keyCode == 189 || e.Keycode == 173)) { + e.preventDefault(); + if (globalScope.scale > 0.5 * DPR) { changeScale(-0.1 * DPR); } + } + + + if (simulationArea.mouseRawX < 0 || simulationArea.mouseRawY < 0 || simulationArea.mouseRawX > width || simulationArea.mouseRawY > height) return; + + scheduleUpdate(1); + updateCanvasSet(true); + + if (simulationArea.lastSelected && simulationArea.lastSelected.keyDown) { + if (e.key.toString().length == 1 || e.key.toString() == 'Backspace') { + simulationArea.lastSelected.keyDown(e.key.toString()); + return; + } + } + if (simulationArea.lastSelected && simulationArea.lastSelected.keyDown2) { + if (e.key.toString().length == 1) { + simulationArea.lastSelected.keyDown2(e.key.toString()); + return; + } + } + + // if (simulationArea.lastSelected && simulationArea.lastSelected.keyDown3) { + // if (e.key.toString() != "Backspace" && e.key.toString() != "Delete") { + // simulationArea.lastSelected.keyDown3(e.key.toString()); + // return; + // } + + // } + + if (e.key == 'T' || e.key == 't') { + simulationArea.changeClockTime(prompt('Enter Time:')); + } + }); + document.getElementById('simulationArea').addEventListener('dblclick', (e) => { + scheduleUpdate(2); + if (simulationArea.lastSelected && simulationArea.lastSelected.dblclick !== undefined) { + simulationArea.lastSelected.dblclick(); + } + }); + + + window.addEventListener('mouseup', (e) => { + simulationArea.mouseDown = false; + errorDetectedSet(false); + updateSimulationSet(true); + updatePositionSet(true); + updateCanvasSet(true); + gridUpdateSet(true); + wireToBeCheckedSet(1); + + scheduleUpdate(1); + }); + window.addEventListener('mousedown', function (e) { + this.focus(); + }); + + document.getElementById('simulationArea').addEventListener('mousewheel', MouseScroll); + document.getElementById('simulationArea').addEventListener('DOMMouseScroll', MouseScroll); + + function MouseScroll(event) { + updateCanvasSet(true); + + event.preventDefault(); + const deltaY = event.wheelDelta ? event.wheelDelta : -event.detail; + const scrolledUp = deltaY < 0; + const scrolledDown = deltaY > 0; + + if (event.ctrlKey) { + if (scrolledUp && globalScope.scale > 0.5 * DPR) { + changeScale(-0.1 * DPR); + } + if (scrolledDown && globalScope.scale < 4 * DPR) { + changeScale(0.1 * DPR); + } + } else { + if (scrolledUp && globalScope.scale < 4 * DPR) { + changeScale(0.1 * DPR); + } + if (scrolledDown && globalScope.scale > 0.5 * DPR) { + changeScale(-0.1 * DPR); + } + } + + updateCanvasSet(true); + gridUpdateSet(true); + update(); // Schedule update not working, this is INEFFICENT + } + + document.addEventListener('cut', (e) => { + simulationArea.copyList = simulationArea.multipleObjectSelections.slice(); + if (simulationArea.lastSelected && simulationArea.lastSelected !== simulationArea.root && !simulationArea.copyList.contains(simulationArea.lastSelected)) { + simulationArea.copyList.push(simulationArea.lastSelected); + } + + const textToPutOnClipboard = cut(simulationArea.copyList); + if (isIe) { + window.clipboardData.setData('Text', textToPutOnClipboard); + } else { + e.clipboardData.setData('text/plain', textToPutOnClipboard); + } + e.preventDefault(); + }); + document.addEventListener('copy', (e) => { + simulationArea.copyList = simulationArea.multipleObjectSelections.slice(); + if (simulationArea.lastSelected && simulationArea.lastSelected !== simulationArea.root && !simulationArea.copyList.contains(simulationArea.lastSelected)) { + simulationArea.copyList.push(simulationArea.lastSelected); + } + + const textToPutOnClipboard = copy(simulationArea.copyList); + if (isIe) { + window.clipboardData.setData('Text', textToPutOnClipboard); + } else { + e.clipboardData.setData('text/plain', textToPutOnClipboard); + } + e.preventDefault(); + }); + + document.addEventListener('paste', (e) => { + let data; + if (isIe) { + data = window.clipboardData.getData('Text'); + } else { + data = e.clipboardData.getData('text/plain'); + } + + paste(data); + e.preventDefault(); + }); +} + + +let isIe = (navigator.userAgent.toLowerCase().indexOf('msie') != -1 + || navigator.userAgent.toLowerCase().indexOf('trident') != -1); diff --git a/src/engine.js b/src/engine.js index 1db494d..8a33d45 100644 --- a/src/engine.js +++ b/src/engine.js @@ -1,232 +1,349 @@ -import { plotArea } from "./plotArea"; -import { simulationArea } from "./simulationArea"; -import { dots, canvasMessage } from "./canvasApi"; -import { showProperties } from "./ux"; -// Engine.js -// Core of the simulation and rendering algorithm - -var totalObjects = 0; - -// Function to check for any UI update, it is throttled by time -export function scheduleUpdate(count = 0, time = 100, fn) { - // console.log(simulationArea.lastSelected) - - if (count && !layoutMode) { // Force update - update(); - for (var i = 0; i < count; i++) - setTimeout(update, 10 + 50 * i); - } - - if (willBeUpdated) return; // Throttling - - willBeUpdated = true; - - if (layoutMode) { - setTimeout(layoutUpdate, time); // Update layout, different algorithm - return; - } - - // Call a function before update .. - if (fn) - setTimeout(function () { - fn(); - update(); - }, time); - else setTimeout(update, time); +/* eslint-disable import/no-cycle */ +/* eslint-disable no-use-before-define */ +/* eslint-disable no-continue */ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-bitwise */ +import { layoutModeGet } from './layoutMode'; +import plotArea from './plotArea'; +import { layoutUpdate } from './layoutMode'; +import simulationArea from './simulationArea'; +import { + dots, canvasMessage, findDimensions, rect2, +} from './canvasApi'; +import { showProperties, prevPropertyObjGet } from './ux'; +import { showError } from './utils'; +import miniMapArea from './minimap'; +import { createNodeGet } from './listeners'; +import { resetup } from './setup'; + +/** + * Core of the simulation and rendering algorithm. + */ + +/** + * @type {number} engine + * @category engine + */ +let wireToBeChecked = 0; + +/** + * Used to set wireChecked boolean which updates wires in UI if true (or 1). 2 if some problem and it is handled. + * @param {number} param - value of wirechecked + * @category engine + */ +export function wireToBeCheckedSet(param) { + wireToBeChecked = param; +} +/** + * scheduleUpdate() will be called if true + * @type {boolean} + * @category engine + */ +let willBeUpdated = false; + +/** + * used to set willBeUpdated variable + * @type {boolean} + * @category engine + * @category engine + */ +export function willBeUpdatedSet(param) { + willBeUpdated = param; } -// fn that calls update on everything else. If any change is there, it resolves the circuit and draws it again +/** + * true if we have an element selected and + * is used when we are paning the grid. + * @type {boolean} + * @category engine + */ +let objectSelection = false; + +/** + * used to set the value of object selection, + * @param {boolean} param + * @category engine + */ +export function objectSelectionSet(param) { + objectSelection = param; +} -export function update(scope = globalScope, updateEverything = false) { +/** + * Flag for updating position + * @type {boolean} + * @category engine + */ +let updatePosition = true; + +/** + * used to set the value of updatePosition. + * @param {boolean} param + * @category engine + */ +export function updatePositionSet(param) { + updatePosition = param; +} - willBeUpdated = false; - if (loading == true || layoutMode) return; +/** + * Flag for updating simulation + * @type {boolean} + * @category engine + */ +let updateSimulation = true; + +/** + * used to set the value of updateSimulation. + * @param {boolean} param + * @category engine + */ +export function updateSimulationSet(param) { + updateSimulation = param; +} +/** + * Flag for rendering + * @type {boolean} + * @category engine + */ +let updateCanvas = true; + +/** + * used to set the value of updateCanvas. + * @param {boolean} param + * @category engine + */ +export function updateCanvasSet(param) { + updateCanvas = param; +} - var updated = false; - simulationArea.hover = undefined; +/** + * Flag for updating grid + * @type {boolean} + * @category engine + */ +let gridUpdate = true; + +/** + * used to set gridUpdate + * @param {boolean} param + * @category engine + */ +export function gridUpdateSet(param) { + gridUpdate = param; +} +/** + * Flag for updating grid + * @type {boolean} + * @category engine + */ +let forceResetNodes = true; + +/** + * used to set forceResetNodes + * @param {boolean} param + * @category engine + */ +export function forceResetNodesSet(param) { + forceResetNodes = param; +} +/** + * Flag for updating grid + * @type {boolean} + * @category engine + */ +let errorDetected = false; + +/** + * used to set errorDetected + * @param {boolean} param + * @category engine + */ +export function errorDetectedSet(param) { + errorDetected = param; +} - // Update wires - if (wireToBeChecked || updateEverything) { - if (wireToBeChecked == 2) wireToBeChecked = 0; // this required due to timing issues - else wireToBeChecked++; - // WHY IS THIS REQUIRED ???? we are checking inside wire ALSO - var prevLength = scope.wires.length; - for (var i = 0; i < scope.wires.length; i++) { - scope.wires[i].checkConnections(); - if (scope.wires.length != prevLength) { - prevLength--; - i--; - } - } - scheduleUpdate(); - } +/** + * used to set errorDetected + * @returns {boolean} errorDetected + * @category engine + */ +export function errorDetectedGet() { + return errorDetected; +} - // Update subcircuits - if (updateSubcircuit || updateEverything) { - for (var i = 0; i < scope.SubCircuit.length; i++) - scope.SubCircuit[i].reset(); - updateSubcircuit = false; +/** + * details of where and what canvas message has to be shown. + * @type {Object} + * @property {number} x - x cordinate of message + * @property {number} y - x cordinate of message + * @property {number} string - the message + * @category engine +*/ +export let canvasMessageData = { + x: undefined, + y: undefined, + string: undefined, +}; + +/** + * Flag for updating subCircuits + * @type {boolean} + * @category engine + */ +let updateSubcircuit = true; + +/** + * used to set updateSubcircuit + * @param {boolean} param + * @category engine + */ +export function updateSubcircuitSet(param) { + // console.log(updateSubcircuit,param); + if (updateSubcircuit != param) { + updateSubcircuit = param; + return true; } + updateSubcircuit = param; + return false; +} - // Update UI position - if (updatePosition || updateEverything) { - for (var i = 0; i < updateOrder.length; i++) - for (var j = 0; j < scope[updateOrder[i]].length; j++) { - updated |= scope[updateOrder[i]][j].update(); - } +/** + * turn light mode on + * @param {boolean} val -- new value for light mode + * @category engine + */ +export function changeLightMode(val) { + if (!val && lightMode) { + lightMode = false; + DPR = window.devicePixelRatio || 1; + globalScope.scale *= DPR; + } else if (val && !lightMode) { + lightMode = true; + globalScope.scale /= DPR; + DPR = 1; + $('#miniMap').fadeOut('fast'); } + resetup(); +} - // Updates multiple objectselections and panes window - if (updatePosition || updateEverything) { - updateSelectionsAndPane(scope); +/** + * Function to render Canvas according th renderupdate order + * @param {Scope} scope - The circuit whose canvas we want to render + * @category engine + */ +export function renderCanvas(scope) { + if (layoutModeGet()) { // Different Algorithm + return; } - - // Update MiniMap - if (!embed && simulationArea.mouseDown && simulationArea.lastSelected && simulationArea.lastSelected != globalScope.root) { - if (!lightMode) - $('#miniMap').fadeOut('fast'); + const ctx = simulationArea.context; + // Reset canvas + simulationArea.clear(); + // Update Grid + if (gridUpdate) { + gridUpdateSet(false); + dots(); } - - // Run simulation - if (updateSimulation) { - play(); + canvasMessageData = { + x: undefined, + y: undefined, + string: undefined, + }; // Globally set in draw fn () + // Render objects + for (let i = 0; i < renderOrder.length; i++) { + for (let j = 0; j < scope[renderOrder[i]].length; j++) { scope[renderOrder[i]][j].draw(); } } - - // Show properties of selected element - if (!embed && prevPropertyObj != simulationArea.lastSelected) { - if (simulationArea.lastSelected && simulationArea.lastSelected.objectType !== "Wire") { - showProperties(simulationArea.lastSelected); - } else { - // hideProperties(); - } + // Show any message + if (canvasMessageData.string !== undefined) { + canvasMessage(ctx, canvasMessageData.string, canvasMessageData.x, canvasMessageData.y); } - - //Draw, render everything - if (updateCanvas) { - renderCanvas(scope); + // If multiple object selections are going on, show selected area + if (objectSelection) { + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'rgba(0,0,0,0.1)'; + rect2(ctx, simulationArea.mouseDownX, simulationArea.mouseDownY, simulationArea.mouseX - simulationArea.mouseDownX, simulationArea.mouseY - simulationArea.mouseDownY, 0, 0, 'RIGHT'); + ctx.stroke(); + ctx.fill(); } - updateSimulation = updateCanvas = updatePosition = false; - -} - -// Function to find dimensions of the current circuit -export function findDimensions(scope = globalScope) { - totalObjects = 0; - simulationArea.minWidth = undefined; - simulationArea.maxWidth = undefined; - simulationArea.minHeight = undefined; - simulationArea.maxHeight = undefined; - for (var i = 0; i < updateOrder.length; i++) { - if (updateOrder[i] !== 'wires') - for (var j = 0; j < scope[updateOrder[i]].length; j++) { - - totalObjects += 1; - var obj = scope[updateOrder[i]][j]; - if (totalObjects == 1) { - simulationArea.minWidth = obj.absX(); - simulationArea.minHeight = obj.absY(); - simulationArea.maxWidth = obj.absX(); - simulationArea.maxHeight = obj.absY(); - } - if (obj.objectType != 'Node') { - if (obj.y - obj.upDimensionY < simulationArea.minHeight) - simulationArea.minHeight = obj.y - obj.upDimensionY; - if (obj.y + obj.downDimensionY > simulationArea.maxHeight) - simulationArea.maxHeight = obj.y + obj.downDimensionY; - if (obj.x - obj.leftDimensionX < simulationArea.minWidth) - simulationArea.minWidth = obj.x - obj.leftDimensionX; - if (obj.x + obj.rightDimensionX > simulationArea.maxWidth) - simulationArea.maxWidth = obj.x + obj.rightDimensionX; - } else { - if (obj.absY() < simulationArea.minHeight) - simulationArea.minHeight = obj.absY(); - if (obj.absY() > simulationArea.maxHeight) - simulationArea.maxHeight = obj.absY(); - if (obj.absX() < simulationArea.minWidth) - simulationArea.minWidth = obj.absX(); - if (obj.absX() > simulationArea.maxWidth) - simulationArea.maxWidth = obj.absX(); - } - - } - + if (simulationArea.hover !== undefined) { + simulationArea.canvas.style.cursor = 'pointer'; + } else if (createNodeGet()) { + simulationArea.canvas.style.cursor = 'grabbing'; + } else { + simulationArea.canvas.style.cursor = 'default'; } - simulationArea.objectList = updateOrder; - } -// Function to move multiple objects and panes window +/** + * Function to move multiple objects and panes window + * deselected using dblclick right now (PR open for esc key) + * @param {Scope=} scope - the circuit in which we are selecting stuff + * @category engine + */ export function updateSelectionsAndPane(scope = globalScope) { - if (!simulationArea.selected && simulationArea.mouseDown) { - simulationArea.selected = true; simulationArea.lastSelected = scope.root; simulationArea.hover = scope.root; - // Selecting multiple objects if (simulationArea.shiftDown) { - objectSelection = true; - } else { - if (!embed) { - findDimensions(scope); - // miniMapArea.setup(); - $('#miniMap').show(); - } + objectSelectionSet(true); + } else if (!embed) { + findDimensions(scope); + miniMapArea.setup(); + $('#miniMap').show(); } - } else if (simulationArea.lastSelected == scope.root && simulationArea.mouseDown) { - - //pane canvas + } else if (simulationArea.lastSelected === scope.root && simulationArea.mouseDown) { + // pane canvas to give an idea of grid moving if (!objectSelection) { globalScope.ox = (simulationArea.mouseRawX - simulationArea.mouseDownRawX) + simulationArea.oldx; globalScope.oy = (simulationArea.mouseRawY - simulationArea.mouseDownRawY) + simulationArea.oldy; globalScope.ox = Math.round(globalScope.ox); globalScope.oy = Math.round(globalScope.oy); - gridUpdate = true; - // if (!embed && !lightMode) miniMapArea.setup(); + gridUpdateSet(true); + if (!embed && !lightMode) miniMapArea.setup(); } else { - + // idea: kind of empty } - - - } else if (simulationArea.lastSelected == scope.root) { - - // Select multiple objects - + } else if (simulationArea.lastSelected === scope.root) { + /* + Select multiple objects by adding them to the array + simulationArea.multipleObjectSelections when we select + using shift + mouse movement to select an area but + not shift + click + */ simulationArea.lastSelected = undefined; simulationArea.selected = false; simulationArea.hover = undefined; - if (objectSelection) { - objectSelection = false; - var x1 = simulationArea.mouseDownX; - var x2 = simulationArea.mouseX; - var y1 = simulationArea.mouseDownY; - var y2 = simulationArea.mouseY; - - // Sort points + objectSelectionSet(false); + let x1 = simulationArea.mouseDownX; + let x2 = simulationArea.mouseX; + let y1 = simulationArea.mouseDownY; + let y2 = simulationArea.mouseY; + // Sort those four points to make a selection pane if (x1 > x2) { - var temp = x1; + const temp = x1; x1 = x2; x2 = temp; } if (y1 > y2) { - var temp = y1; + const temp = y1; y1 = y2; y2 = temp; } - // Select the objects, push them into a list - for (var i = 0; i < updateOrder.length; i++) { - for (var j = 0; j < scope[updateOrder[i]].length; j++) { - var obj = scope[updateOrder[i]][j]; + for (let i = 0; i < updateOrder.length; i++) { + for (let j = 0; j < scope[updateOrder[i]].length; j++) { + const obj = scope[updateOrder[i]][j]; if (simulationArea.multipleObjectSelections.contains(obj)) continue; - var x, y; - if (obj.objectType == "Node") { + let x; var + y; + if (obj.objectType === 'Node') { x = obj.absX(); y = obj.absY(); - } else if (obj.objectType != "Wire") { + } else if (obj.objectType !== 'Wire') { x = obj.x; y = obj.y; } else { @@ -241,115 +358,166 @@ export function updateSelectionsAndPane(scope = globalScope) { } } -// Function to render Canvas according th renderupdate order -export function renderCanvas(scope) { - - if (layoutMode) { // Different Algorithm - return; - } - - var ctx = simulationArea.context; - - // Reset canvas - simulationArea.clear(); - - // Update Grid - if (gridUpdate) { - gridUpdate = false; - dots(); - } - - canvasMessageData = undefined; // Globally set in draw fn () - - // Render objects - for (var i = 0; i < renderOrder.length; i++) - for (var j = 0; j < scope[renderOrder[i]].length; j++) - scope[renderOrder[i]][j].draw(); - - // Show any message - - if (canvasMessageData) { - canvasMessage(ctx, canvasMessageData.string, canvasMessageData.x, canvasMessageData.y) - } - - // If multiple object selections are going on, show selected area - if (objectSelection) { - ctx.beginPath(); - ctx.lineWidth = 2; - ctx.strokeStyle = "black" - ctx.fillStyle = "rgba(0,0,0,0.1)" - rect2(ctx, simulationArea.mouseDownX, simulationArea.mouseDownY, simulationArea.mouseX - simulationArea.mouseDownX, simulationArea.mouseY - simulationArea.mouseDownY, 0, 0, "RIGHT"); - ctx.stroke(); - ctx.fill(); - } - -} - -//Main fn that resolves circuit using event driven simulation -function play(scope = globalScope, resetNodes = false) { - +/** + * Main fn that resolves circuit using event driven simulation + * All inputs are added to a scope using scope.addinput() and + * the simulation starts to play. + * @param {Scope=} scope - the circuit we want to simulate + * @param {boolean} resetNodes - boolean to reset all nodes + * @category engine + */ +export function play(scope = globalScope, resetNodes = false) { if (errorDetected) return; // Don't simulate until error is fixed - - if (loading == true) return; // Don't simulate until loaded + if (loading === true) return; // Don't simulate until loaded if (!embed) plotArea.stopWatch.Stop(); // Waveform thing - // Reset Nodes if required if (resetNodes || forceResetNodes) { scope.reset(); simulationArea.simulationQueue.reset(); - forceResetNodes = false; + forceResetNodesSet(false); } - // Temporarily kept for for future uses // else{ // // clearBuses(scope); - // for(var i=0;i 1000000) { // Cyclic or infinite Circuit Detection - showError("Simulation Stack limit exceeded: maybe due to cyclic paths or contention"); - errorDetected = true; - forceResetNodes = true + showError('Simulation Stack limit exceeded: maybe due to cyclic paths or contention'); + errorDetectedSet(true); + forceResetNodesSet(true); } } - // Check for TriState Contentions if (simulationArea.contentionPending.length) { - showError("Contention at TriState"); - forceResetNodes = true - errorDetected = true; + console.log(simulationArea.contentionPending); + showError('Contention at TriState'); + forceResetNodesSet(true); + errorDetectedSet(true); } - // Setting Flag Values - for (var i = 0; i < scope.Flag.length; i++) - scope.Flag[i].setPlotValue(); + for (let i = 0; i < scope.Flag.length; i++) { scope.Flag[i].setPlotValue(); } +} + +/** + * Function to check for any UI update, it is throttled by time + * @param {number=} count - this is used to force update + * @param {number=} time - the time throttling parameter + * @param {function} fn - function to run before updating UI + * @category engine + */ +export function scheduleUpdate(count = 0, time = 100, fn) { + if (lightMode) time *= 5; + if (count && !layoutModeGet()) { // Force update + update(); + for (let i = 0; i < count; i++) { setTimeout(update, 10 + 50 * i); } + } + if (willBeUpdated) return; // Throttling + willBeUpdatedSet(true); + if (layoutModeGet()) { + setTimeout(layoutUpdate, time); // Update layout, different algorithm + return; + } + // Call a function before update .. + if (fn) { + setTimeout(() => { + fn(); + update(); + }, time); + } else setTimeout(update, time); +} +/** + * fn that calls update on everything else. If any change + * is there, it resolves the circuit and draws it again. + * Also updates simulations, selection, minimap, resolves + * circuit and redraws canvas if required. + * @param {Scope=} scope - the circuit to be updated + * @param {boolean=} updateEverything - if true we update the wires, nodes and modules + * @category engine + */ +export function update(scope = globalScope, updateEverything = false) { + willBeUpdatedSet(false); + if (loading === true || layoutModeGet()) return; + let updated = false; + simulationArea.hover = undefined; + // Update wires + if (wireToBeChecked || updateEverything) { + if (wireToBeChecked === 2) wireToBeChecked = 0; // this required due to timing issues + else wireToBeChecked++; + // WHY IS THIS REQUIRED ???? we are checking inside wire ALSO + // Idea: we can just call length again instead of doing it during loop. + let prevLength = scope.wires.length; + for (let i = 0; i < scope.wires.length; i++) { + scope.wires[i].checkConnections(); + if (scope.wires.length !== prevLength) { + prevLength--; + i--; + } + } + scheduleUpdate(); + } + // Update subcircuits + if (updateSubcircuit || updateEverything) { + for (let i = 0; i < scope.SubCircuit.length; i++) { scope.SubCircuit[i].reset(); } + updateSubcircuitSet(false); + } + // Update UI position + if (updatePosition || updateEverything) { + for (let i = 0; i < updateOrder.length; i++) { + for (let j = 0; j < scope[updateOrder[i]].length; j++) { + updated |= scope[updateOrder[i]][j].update(); + } + } + } + // Updates multiple objectselections and panes window + if (updatePosition || updateEverything) { + updateSelectionsAndPane(scope); + } + // Update MiniMap + if (!embed && simulationArea.mouseDown && simulationArea.lastSelected && simulationArea.lastSelected !== globalScope.root) { + if (!lightMode) { $('#miniMap').fadeOut('fast'); } + } + // Run simulation + if (updateSimulation) { + play(); + } + // Show properties of selected element + if (!embed && prevPropertyObjGet() !== simulationArea.lastSelected) { + if (simulationArea.lastSelected && simulationArea.lastSelected.objectType !== 'Wire') { + // ideas: why show properties of project in Nodes but not wires? + showProperties(simulationArea.lastSelected); + } else { + // hideProperties(); + } + } + // Draw, render everything + if (updateCanvas) { + renderCanvas(scope); + } + updateSimulationSet(false); + updateCanvas = false; + updatePositionSet(false); } diff --git a/src/eventQueue.js b/src/eventQueue.js index aca4c10..cfc6a35 100644 --- a/src/eventQueue.js +++ b/src/eventQueue.js @@ -1,5 +1,7 @@ -// Event Queue is simply a priority Queue, basic implementation O(n^2) - +/** + * Event Queue is simply a priority Queue, basic implementation O(n^2). + * @category eventQueue + */ export default class EventQueue { constructor(size) { this.size = size; @@ -8,13 +10,16 @@ export default class EventQueue { this.time = 0; } + /** + * @param {CircuitElement} obj - the elemnt to be added + * @param {number} delay - the delay in adding an object to queue + */ add(obj, delay) { - if (obj.queueProperties.inQueue) { obj.queueProperties.time = this.time + (delay || obj.propagationDelay); let i = obj.queueProperties.index; while (i > 0 && obj.queueProperties.time > this.queue[i - 1].queueProperties.time) { - this.swap(i, i - 1) + this.swap(i, i - 1); i--; } i = obj.queueProperties.index; @@ -25,7 +30,7 @@ export default class EventQueue { return; } - if (this.frontIndex == this.size) throw "EventQueue size exceeded"; + if (this.frontIndex == this.size) throw 'EventQueue size exceeded'; this.queue[this.frontIndex] = obj; // console.log(this.time) // obj.queueProperties.time=obj.propagationDelay; @@ -35,11 +40,15 @@ export default class EventQueue { obj.queueProperties.inQueue = true; let i = obj.queueProperties.index; while (i > 0 && obj.queueProperties.time > this.queue[i - 1].queueProperties.time) { - this.swap(i, i - 1) + this.swap(i, i - 1); i--; } - } + + /** + * To add without any delay. + * @param {CircuitElement} obj - the object to be added + */ addImmediate(obj) { this.queue[this.frontIndex] = obj; obj.queueProperties.time = this.time; @@ -48,36 +57,48 @@ export default class EventQueue { this.frontIndex++; } + /** + * Function to swap two objects in queue. + * @param {number} v1 + * @param {number} v2 + */ swap(v1, v2) { - let obj1 = this.queue[v1]; + const obj1 = this.queue[v1]; obj1.queueProperties.index = v2; - let obj2 = this.queue[v2]; + const obj2 = this.queue[v2]; obj2.queueProperties.index = v1; this.queue[v1] = obj2; this.queue[v2] = obj1; } + /** + * function to pop element from queue. + */ pop() { - if (this.isEmpty()) throw "Queue Empty" + if (this.isEmpty()) throw 'Queue Empty'; this.frontIndex--; - let obj = this.queue[this.frontIndex] + const obj = this.queue[this.frontIndex]; this.time = obj.queueProperties.time; obj.queueProperties.inQueue = false; return obj; } + /** + * function to reset queue. + */ reset() { - for (let i = 0; i < this.frontIndex; i++) - this.queue[i].queueProperties.inQueue = false - this.time = 0 + for (let i = 0; i < this.frontIndex; i++) this.queue[i].queueProperties.inQueue = false; + this.time = 0; this.frontIndex = 0; } + /** + * function to check if empty queue. + */ isEmpty() { return this.frontIndex == 0; } - } diff --git a/src/events.js b/src/events.js new file mode 100755 index 0000000..2441214 --- /dev/null +++ b/src/events.js @@ -0,0 +1,293 @@ +/* eslint-disable import/no-cycle */ +import Scope, { scopeList, switchCircuit, newCircuit } from './circuit'; + +import { loadScope } from './data/load'; +import { + scheduleUpdate, updateSimulationSet, updateSubcircuitSet, forceResetNodesSet, +} from './engine'; +import { backUp } from './data/backupCircuit'; +import { getNextPosition } from './modules'; +import { generateId } from './utils'; +import simulationArea from './simulationArea'; + +/** + * Helper function to paste + * @param {JSON} copyData - the data to be pasted + * @category events + */ +export function paste(copyData) { + if (copyData === undefined) return; + const data = JSON.parse(copyData); + if (!data.logixClipBoardData) return; + + const currentScopeId = globalScope.id; + for (let i = 0; i < data.scopes.length; i++) { + if (scopeList[data.scopes[i].id] === undefined) { + const scope = newCircuit(data.scopes[i].name, data.scopes[i].id); + loadScope(scope, data.scopes[i]); + scopeList[data.scopes[i].id] = scope; + } + } + + switchCircuit(currentScopeId); + const tempScope = new Scope(globalScope.name, globalScope.id); + const oldOx = globalScope.ox; + const oldOy = globalScope.oy; + const oldScale = globalScope.scale; + loadScope(tempScope, data); + + let prevLength = tempScope.allNodes.length; + for (let i = 0; i < tempScope.allNodes.length; i++) { + tempScope.allNodes[i].checkDeleted(); + if (tempScope.allNodes.length != prevLength) { + prevLength--; + i--; + } + } + + let approxX = 0; + let approxY = 0; + let count = 0; + + for (let i = 0; i < updateOrder.length; i++) { + for (let j = 0; j < tempScope[updateOrder[i]].length; j++) { + const obj = tempScope[updateOrder[i]][j]; + obj.updateScope(globalScope); + if (obj.objectType != 'Wire') { + approxX += obj.x; + approxY += obj.y; + count++; + } + } + } + + for (let j = 0; j < tempScope.CircuitElement.length; j++) { + const obj = tempScope.CircuitElement[j]; + obj.updateScope(globalScope); + } + + approxX /= count; + approxY /= count; + + approxX = Math.round(approxX / 10) * 10; + approxY = Math.round(approxY / 10) * 10; + + + for (let i = 0; i < updateOrder.length; i++) { + for (let j = 0; j < tempScope[updateOrder[i]].length; j++) { + const obj = tempScope[updateOrder[i]][j]; + if (obj.objectType !== 'Wire') { + obj.x += simulationArea.mouseX - approxX; + obj.y += simulationArea.mouseY - approxY; + } + } + } + + Object.keys(tempScope).forEach((l) => { + if (tempScope[l] instanceof Array && l !== 'objects' && l !== 'CircuitElement') { + globalScope[l].extend(tempScope[l]); + } + }); + for (let i = 0; i < tempScope.Input.length; i++) { + tempScope.Input[i].layoutProperties.y = getNextPosition(0, globalScope); + tempScope.Input[i].layoutProperties.id = generateId(); + } + for (let i = 0; i < tempScope.Output.length; i++) { + tempScope.Output[i].layoutProperties.x = globalScope.layout.width; + tempScope.Output[i].layoutProperties.id = generateId(); + tempScope.Output[i].layoutProperties.y = getNextPosition(globalScope.layout.width, globalScope); + } + const canvasUpdate = true; + updateSimulationSet(true); + updateSubcircuitSet(true); + scheduleUpdate(); + globalScope.ox = oldOx; + globalScope.oy = oldOy; + globalScope.scale = oldScale; + + + forceResetNodesSet(true); +} +/** + * Helper function for cut + * @param {JSON} copyList - The selected elements + * @category events + */ +export function cut(copyList) { + if (copyList.length === 0) return; + const tempScope = new Scope(globalScope.name, globalScope.id); + const oldOx = globalScope.ox; + const oldOy = globalScope.oy; + const oldScale = globalScope.scale; + d = backUp(globalScope); + loadScope(tempScope, d); + scopeList[tempScope.id] = tempScope; + + for (let i = 0; i < copyList.length; i++) { + const obj = copyList[i]; + if (obj.objectType === 'Node') obj.objectType = 'allNodes'; + for (let j = 0; j < tempScope[obj.objectType].length; j++) { + if (tempScope[obj.objectType][j].x === obj.x && tempScope[obj.objectType][j].y === obj.y && (obj.objectType != 'Node' || obj.type === 2)) { + tempScope[obj.objectType][j].delete(); + break; + } + } + } + tempScope.backups = globalScope.backups; + for (let i = 0; i < updateOrder.length; i++) { + let prevLength = globalScope[updateOrder[i]].length; // LOL length of list will reduce automatically when deletion starts + for (let j = 0; j < globalScope[updateOrder[i]].length; j++) { + const obj = globalScope[updateOrder[i]][j]; + if (obj.objectType != 'Wire') { // }&&obj.objectType!='CircuitElement'){//}&&(obj.objectType!='Node'||obj.type==2)){ + if (!copyList.contains(globalScope[updateOrder[i]][j])) { + globalScope[updateOrder[i]][j].cleanDelete(); + } + } + + if (globalScope[updateOrder[i]].length != prevLength) { + prevLength--; + j--; + } + } + } + + let prevLength = globalScope.wires.length; + for (let i = 0; i < globalScope.wires.length; i++) { + globalScope.wires[i].checkConnections(); + if (globalScope.wires.length != prevLength) { + prevLength--; + i--; + } + } + + updateSimulationSet(true); + + let data = backUp(globalScope); + data.logixClipBoardData = true; + const dependencyList = globalScope.getDependencies(); + data.dependencies = {}; + Object.keys(dependencyList).forEach((dependency) => { + data.dependencies[dependency] = backUp(scopeList[dependency]); + }); + data.logixClipBoardData = true; + data = JSON.stringify(data); + + simulationArea.multipleObjectSelections = []; // copyList.slice(); + simulationArea.copyList = []; // copyList.slice(); + const canvasUpdate = true; + updateSimulationSet(true); + globalScope = tempScope; + scheduleUpdate(); + globalScope.ox = oldOx; + globalScope.oy = oldOy; + globalScope.scale = oldScale; + forceResetNodesSet(true); + // eslint-disable-next-line consistent-return + return data; +} +/** + * Helper function for copy + * @param {JSON} copyList - The data to copied + * @param {boolean} cutflag - flase if we want to copy + * @category events + */ +export function copy(copyList, cutflag = false) { + if (copyList.length === 0) return; + const tempScope = new Scope(globalScope.name, globalScope.id); + const oldOx = globalScope.ox; + const oldOy = globalScope.oy; + const oldScale = globalScope.scale; + const d = backUp(globalScope); + + loadScope(tempScope, d); + scopeList[tempScope.id] = tempScope; + + if (cutflag) { + for (let i = 0; i < copyList.length; i++) { + const obj = copyList[i]; + if (obj.objectType === 'Node') obj.objectType = 'allNodes'; + for (let j = 0; j < tempScope[obj.objectType].length; j++) { + if (tempScope[obj.objectType][j].x === obj.x && tempScope[obj.objectType][j].y === obj.y && (obj.objectType != 'Node' || obj.type === 2)) { + tempScope[obj.objectType][j].delete(); + break; + } + } + } + } + tempScope.backups = globalScope.backups; + for (let i = 0; i < updateOrder.length; i++) { + let prevLength = globalScope[updateOrder[i]].length; // LOL length of list will reduce automatically when deletion starts + for (let j = 0; j < globalScope[updateOrder[i]].length; j++) { + const obj = globalScope[updateOrder[i]][j]; + if (obj.objectType != 'Wire') { // }&&obj.objectType!='CircuitElement'){//}&&(obj.objectType!='Node'||obj.type==2)){ + if (!copyList.contains(globalScope[updateOrder[i]][j])) { + // //console.log("DELETE:", globalScope[updateOrder[i]][j]); + globalScope[updateOrder[i]][j].cleanDelete(); + } + } + + if (globalScope[updateOrder[i]].length != prevLength) { + prevLength--; + j--; + } + } + } + + let prevLength = globalScope.wires.length; + for (let i = 0; i < globalScope.wires.length; i++) { + globalScope.wires[i].checkConnections(); + if (globalScope.wires.length != prevLength) { + prevLength--; + i--; + } + } + + updateSimulationSet(true); + + let data = backUp(globalScope); + data.scopes = []; + const dependencyList = {}; + const requiredDependencies = globalScope.getDependencies(); + const completed = {}; + Object.keys(scopeList).forEach((id) => { + dependencyList[id] = scopeList[id].getDependencies(); + }); + function saveScope(id) { + if (completed[id]) return; + for (let i = 0; i < dependencyList[id].length; i++) { saveScope(dependencyList[id][i]); } + completed[id] = true; + data.scopes.push(backUp(scopeList[id])); + } + for (let i = 0; i < requiredDependencies.length; i++) { saveScope(requiredDependencies[i]); } + data.logixClipBoardData = true; + data = JSON.stringify(data); + simulationArea.multipleObjectSelections = []; // copyList.slice(); + simulationArea.copyList = []; // copyList.slice(); + const canvasUpdate = true; + updateSimulationSet(true); + globalScope = tempScope; + scheduleUpdate(); + globalScope.ox = oldOx; + globalScope.oy = oldOy; + globalScope.scale = oldScale; + forceResetNodesSet(true); + // needs to be fixed + // eslint-disable-next-line consistent-return + return data; +} + +/** + * Function selects all the elements from the scope + * @category events + */ +export function selectAll(scope = globalScope) { + circuitElementList.forEach((val, _, __) => { + if (scope.hasOwnProperty(val)) { + simulationArea.multipleObjectSelections.push(...scope[val]); + } + }); + + if (scope.nodes) { + simulationArea.multipleObjectSelections.push(...scope.nodes); + } +} diff --git a/src/layout/layoutBuffer.js b/src/layout/layoutBuffer.js new file mode 100755 index 0000000..07b7342 --- /dev/null +++ b/src/layout/layoutBuffer.js @@ -0,0 +1,61 @@ +import LayoutNode from './layoutNode'; +/** + * Buffer object to store changes so that you can reset changes + * @class + * @param {Scope=} scope + * @category layout + */ +export default class LayoutBuffer { + constructor(scope = globalScope) { + // Position of screen in layoutMode -- needs to be deprecated, reset screen position instead + const x = -Math.round(globalScope.ox / 10) * 10; + const y = -Math.round(globalScope.oy / 10) * 10; + + const w = Math.round((width / globalScope.scale) * 0.01) * 10; // 10% width of screen in layoutMode + const h = Math.round((height / globalScope.scale) * 0.01) * 10; // 10% height of screen in layoutMode + + const xx = x + w; + const yy = y + h; + + // Position of subcircuit + this.xx = xx; + this.yy = yy; + + // Assign layout if exist or create new one + this.layout = { ...scope.layout }; // Object.create(scope.layout); + + // Push Input Nodes + this.Input = []; + for (let i = 0; i < scope.Input.length; i++) { this.Input.push(new LayoutNode(scope.Input[i].layoutProperties.x, scope.Input[i].layoutProperties.y, scope.Input[i].layoutProperties.id, scope.Input[i].label, xx, yy, scope.Input[i].type, scope.Input[i])); } + + // Push Output Nodes + this.Output = []; + for (let i = 0; i < scope.Output.length; i++) { this.Output.push(new LayoutNode(scope.Output[i].layoutProperties.x, scope.Output[i].layoutProperties.y, scope.Output[i].layoutProperties.id, scope.Output[i].label, xx, yy, scope.Output[i].type, scope.Output[i])); } + } + + /** + * @memberof layoutBuffer + * Check if position is on the boundaries of subcircuit + * if the desired width and heiht is allowed + */ + isAllowed(x, y) { + if (x < 0 || x > this.layout.width || y < 0 || y > this.layout.height) return false; + if (x > 0 && x < this.layout.width && y > 0 && y < this.layout.height) return false; + + if ((x === 0 && y === 0) || (x === 0 && y === this.layout.height) || (x === this.layout.width && y === 0) || (x === this.layout.width && y === this.layout.height)) return false; + + return true; + } + + /** + * @memberof layoutBuffer + * Check if node is already at a position + * Function is called while decreasing height to + * check if it is possible without moving other node + */ + isNodeAt(x, y) { + for (let i = 0; i < this.Input.length; i++) { if (this.Input[i].x === x && this.Input[i].y === y) return true; } + for (let i = 0; i < this.Output.length; i++) { if (this.Output[i].x === x && this.Output[i].y === y) return true; } + return false; + } +} diff --git a/src/layout/layoutNode.js b/src/layout/layoutNode.js new file mode 100755 index 0000000..9558523 --- /dev/null +++ b/src/layout/layoutNode.js @@ -0,0 +1,103 @@ +import { drawCircle } from '../canvasApi'; +import simulationArea from '../simulationArea'; +import { tempBuffer } from '../layoutMode'; + +/** + * @class + * @param {number} x - x coord of node + * @param {number} y - y coord of node + * @param {strng} id - id for node + * @param {string=} label - label for the node + * @param {number} xx - parent x + * @param {number} yy - parent y + * @param {number} type - input or output node + * @param {CircuitElement} parent parent of the node + * @category layout + */ +export default class LayoutNode { + constructor(x, y, id, label = '', xx, yy, type, parent) { + this.type = type; + this.id = id; + + this.xx = xx; // Position of parent + this.yy = yy; // Position of parent + this.label = label; + + this.prevx = undefined; + this.prevy = undefined; + this.x = x; // Position of node wrt to parent + this.y = y; // Position of node wrt to parent + + this.radius = 5; + this.clicked = false; + this.hover = false; + this.wasClicked = false; + this.prev = 'a'; + this.count = 0; + this.parent = parent; + } + + absX() { + return this.x + this.xx; + } + + absY() { + return this.y + this.yy; + } + + update() { + // Code copied from node.update() - Some code is redundant - needs to be removed + + if (this === simulationArea.hover) simulationArea.hover = undefined; + this.hover = this.isHover(); + + if (!simulationArea.mouseDown) { + if (this.absX() !== this.prevx || this.absY() !== this.prevy) { + // Store position before clicked + this.prevx = this.absX(); + this.prevy = this.absY(); + } + } + + if (this.hover) { + simulationArea.hover = this; + } + + if (simulationArea.mouseDown && ((this.hover && !simulationArea.selected) || simulationArea.lastSelected === this)) { + simulationArea.selected = true; + simulationArea.lastSelected = this; + this.clicked = true; + } else { + this.clicked = false; + } + + if (!this.wasClicked && this.clicked) { + this.wasClicked = true; + this.prev = 'a'; + simulationArea.lastSelected = this; + } else if (this.wasClicked && this.clicked) { + // Check if valid position and update accordingly + if (tempBuffer.isAllowed(simulationArea.mouseX - this.xx, simulationArea.mouseY - this.yy) && !tempBuffer.isNodeAt(simulationArea.mouseX - this.xx, simulationArea.mouseY - this.yy)) { + this.x = simulationArea.mouseX - this.xx; + this.y = simulationArea.mouseY - this.yy; + } + } + } + + /** + * @memberof layoutNode + * this function is used to draw the nodes + */ + draw() { + const ctx = simulationArea.context; + drawCircle(ctx, this.absX(), this.absY(), 3, ['green', 'red'][+(simulationArea.lastSelected === this)]); + } + + /** + * @memberof layoutNode + * this function is used to check if hover + */ + isHover() { + return this.absX() === simulationArea.mouseX && this.absY() === simulationArea.mouseY; + } +} diff --git a/src/layoutMode.js b/src/layoutMode.js new file mode 100755 index 0000000..08bf5aa --- /dev/null +++ b/src/layoutMode.js @@ -0,0 +1,388 @@ +/* eslint-disable import/no-cycle */ +/* eslint-disable no-continue */ +import { + dots, correctWidth, fillText, rect2, +} from './canvasApi'; +import LayoutBuffer from './layout/layoutBuffer'; +import simulationArea from './simulationArea'; +import { hideProperties } from './ux'; +import { + update, scheduleUpdate, willBeUpdatedSet, gridUpdateSet, +} from './engine'; +import miniMapArea from './minimap'; +import { showMessage } from './utils'; + +/** + * Layout.js - all subcircuit layout related code is here + * You can edit how your subcircuit for a circuit will look by + * clicking edit layout in properties for a ciruit + * @category layoutMode + */ + +let layoutMode = false; + +export function layoutModeSet(param) { + layoutMode = param; +} + +export function layoutModeGet(param) { + return layoutMode; +} + +/** + * @type {LayoutBuffer} - used to temporartily store all changes. + * @category layoutMode + */ +export let tempBuffer; + +/** + * Helper function to determine alignment and position of nodes for rendering + * @param {number} x - width of label + * @param {number} y - height of label + * @category layoutMode + */ +export function determineLabel(x, y) { + if (x === 0) return ['left', 5, 5]; + if (x === tempBuffer.layout.width) return ['right', -5, 5]; + if (y === 0) return ['center', 0, 13]; + return ['center', 0, -6]; +} + +/** + * Used to move the grid in the layout mode + * @param {Scope} scope - the circuit whose subcircuit we are editing + * @category layoutMode + */ +export function paneLayout(scope = globalScope) { + if (!simulationArea.selected && simulationArea.mouseDown) { + simulationArea.selected = true; + simulationArea.lastSelected = scope.root; + simulationArea.hover = scope.root; + } else if (simulationArea.lastSelected === scope.root && simulationArea.mouseDown) { + // pane canvas + if (true) { + globalScope.ox = (simulationArea.mouseRawX - simulationArea.mouseDownRawX) + simulationArea.oldx; + globalScope.oy = (simulationArea.mouseRawY - simulationArea.mouseDownRawY) + simulationArea.oldy; + globalScope.ox = Math.round(globalScope.ox); + globalScope.oy = Math.round(globalScope.oy); + gridUpdateSet(true); + if (!embed && !lightMode) miniMapArea.setup(); + } + } else if (simulationArea.lastSelected === scope.root) { + // Select multiple objects + + simulationArea.lastSelected = undefined; + simulationArea.selected = false; + simulationArea.hover = undefined; + } +} + + +/** + * Function to render layout on screen + * @param {Scope=} scope + * @category layoutMode + */ +export function renderLayout(scope = globalScope) { + if (!layoutModeGet()) return; + const { xx } = tempBuffer; + const { yy } = tempBuffer; + const ctx = simulationArea.context; + simulationArea.clear(); + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + // Draw base rectangle + ctx.beginPath(); + rect2(ctx, 0, 0, tempBuffer.layout.width, tempBuffer.layout.height, xx, yy, 'RIGHT'); + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + ctx.textAlign = 'center'; + ctx.fillStyle = 'black'; + if (tempBuffer.layout.titleEnabled) { + fillText(ctx, scope.name, tempBuffer.layout.title_x + xx, yy + tempBuffer.layout.title_y, 11); + } + + // Draw labels + let info; + for (let i = 0; i < tempBuffer.Input.length; i++) { + if (!tempBuffer.Input[i].label) continue; + info = determineLabel(tempBuffer.Input[i].x, tempBuffer.Input[i].y, scope); + [ctx.textAlign] = info; + fillText(ctx, tempBuffer.Input[i].label, tempBuffer.Input[i].x + info[1] + xx, yy + tempBuffer.Input[i].y + info[2], 12); + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + if (!tempBuffer.Output[i].label) continue; + info = determineLabel(tempBuffer.Output[i].x, tempBuffer.Output[i].y, scope); + [ctx.textAlign] = info; + fillText(ctx, tempBuffer.Output[i].label, tempBuffer.Output[i].x + info[1] + xx, yy + tempBuffer.Output[i].y + info[2], 12); + } + ctx.fill(); + + // Draw points + for (let i = 0; i < tempBuffer.Input.length; i++) { + tempBuffer.Input[i].draw(); + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + tempBuffer.Output[i].draw(); + } + + if (gridUpdateSet(false)) { + dots(); + } +} + +/** + * Update UI, positions of inputs and outputs + * @param {Scope} scope - the circuit whose subcircuit we are editing + * @category layoutMode + */ +export function layoutUpdate(scope = globalScope) { + if (!layoutModeGet()) return; + willBeUpdatedSet(false); + for (let i = 0; i < tempBuffer.Input.length; i++) { + tempBuffer.Input[i].update(); + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + tempBuffer.Output[i].update(); + } + paneLayout(scope); + renderLayout(scope); +} + +/** + * Helper function to reset all nodes to original default positions + * @category layoutMode + */ +export function layoutResetNodes() { + tempBuffer.layout.width = 100; + tempBuffer.layout.height = Math.max(tempBuffer.Input.length, tempBuffer.Output.length) * 20 + 20; + for (let i = 0; i < tempBuffer.Input.length; i++) { + tempBuffer.Input[i].x = 0; + tempBuffer.Input[i].y = i * 20 + 20; + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + tempBuffer.Output[i].x = tempBuffer.layout.width; + tempBuffer.Output[i].y = i * 20 + 20; + } +} + +/** + * Increase width, and move all nodes + * @category layoutMode + */ +export function increaseLayoutWidth() { + for (let i = 0; i < tempBuffer.Input.length; i++) { + if (tempBuffer.Input[i].x === tempBuffer.layout.width) { tempBuffer.Input[i].x += 10; } + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + if (tempBuffer.Output[i].x === tempBuffer.layout.width) { tempBuffer.Output[i].x += 10; } + } + tempBuffer.layout.width += 10; +} + +/** + * Increase Height, and move all nodes + * @category layoutMode + */ +export function increaseLayoutHeight() { + for (let i = 0; i < tempBuffer.Input.length; i++) { + if (tempBuffer.Input[i].y === tempBuffer.layout.height) { tempBuffer.Input[i].y += 10; } + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + if (tempBuffer.Output[i].y === tempBuffer.layout.height) { tempBuffer.Output[i].y += 10; } + } + tempBuffer.layout.height += 10; +} + +/** + * Decrease Width, and move all nodes, check if space is there + * @category layoutMode + */ +export function decreaseLayoutWidth() { + if (tempBuffer.layout.width < 30) return; + for (let i = 0; i < tempBuffer.Input.length; i++) { + if (tempBuffer.Input[i].x === tempBuffer.layout.width - 10) { + showMessage('No space. Move or delete some nodes to make space.'); + return; + } + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + if (tempBuffer.Output[i].x === tempBuffer.layout.width - 10) { + showMessage('No space. Move or delete some nodes to make space.'); + return; + } + } + + for (let i = 0; i < tempBuffer.Input.length; i++) { + if (tempBuffer.Input[i].x === tempBuffer.layout.width) { tempBuffer.Input[i].x -= 10; } + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + if (tempBuffer.Output[i].x === tempBuffer.layout.width) { tempBuffer.Output[i].x -= 10; } + } + tempBuffer.layout.width -= 10; +} + +/** + * Decrease Height, and move all nodes, check if space is there + * @category layoutMode + */ +export function decreaseLayoutHeight() { + if (tempBuffer.layout.height < 30) return; + for (let i = 0; i < tempBuffer.Input.length; i++) { + if (tempBuffer.Input[i].y === tempBuffer.layout.height - 10) { + showMessage('No space. Move or delete some nodes to make space.'); + return; + } + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + if (tempBuffer.Output[i].y === tempBuffer.layout.height - 10) { + showMessage('No space. Move or delete some nodes to make space.'); + return; + } + } + + for (let i = 0; i < tempBuffer.Input.length; i++) { + if (tempBuffer.Input[i].y === tempBuffer.layout.height) { tempBuffer.Input[i].y -= 10; } + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + if (tempBuffer.Output[i].y === tempBuffer.layout.height) { tempBuffer.Output[i].y -= 10; } + } + tempBuffer.layout.height -= 10; +} + +/** + * Helper functions to move the titles + * @category layoutMode + */ +export function layoutTitleUp() { + tempBuffer.layout.title_y -= 5; +} + +/** + * Helper functions to move the titles + * @category layoutMode + */ +export function layoutTitleDown() { + tempBuffer.layout.title_y += 5; +} + +/** + * Helper functions to move the titles + * @category layoutMode + */ +export function layoutTitleRight() { + tempBuffer.layout.title_x += 5; +} + +/** + * Helper functions to move the titles + * @category layoutMode + */ +export function layoutTitleLeft() { + tempBuffer.layout.title_x -= 5; +} + +/** + * Helper functions to move the titles + * @category layoutMode + */ +export function toggleLayoutTitle() { + tempBuffer.layout.titleEnabled = !tempBuffer.layout.titleEnabled; +} + +/** + * just toggles back to normal mode + * @category layoutMode + */ +function cancelLayout() { + if (layoutModeGet()) { + // eslint-disable-next-line no-use-before-define + toggleLayoutMode(); + } +} + + +/** + * Store all data into layout and exit + * @category layoutMode + */ +function saveLayout() { + if (layoutModeGet()) { + for (let i = 0; i < tempBuffer.Input.length; i++) { + tempBuffer.Input[i].parent.layoutProperties.x = tempBuffer.Input[i].x; + tempBuffer.Input[i].parent.layoutProperties.y = tempBuffer.Input[i].y; + } + for (let i = 0; i < tempBuffer.Output.length; i++) { + tempBuffer.Output[i].parent.layoutProperties.x = tempBuffer.Output[i].x; + tempBuffer.Output[i].parent.layoutProperties.y = tempBuffer.Output[i].y; + } + globalScope.layout = { ...tempBuffer.layout }; + // eslint-disable-next-line no-use-before-define + toggleLayoutMode(); + } +} + +/** + * Function to toggle between layoutMode and normal Mode + * the sidebar is disabled and n properties are shown. + * @category layoutMode + */ +export function toggleLayoutMode() { + if (layoutModeGet()) { + (layoutModeSet(false)); + $('#layoutDialog').fadeOut(); + globalScope.centerFocus(false); + dots(); + } else { + (layoutModeSet(true)); + $('#layoutDialog').fadeIn(); + globalScope.ox = 0; + globalScope.oy = 0; + globalScope.scale = DPR * 1.3; + dots(); + tempBuffer = new LayoutBuffer(); + $('#toggleLayoutTitle')[0].checked = tempBuffer.layout.titleEnabled; + } + hideProperties(); + update(globalScope, true); + scheduleUpdate(); + // console.log(document.querySelector('#layoutDialog')) + $('#decreaseLayoutWidth').click(() => { + decreaseLayoutWidth(); + }); + $('#increaseLayoutWidth').click(() => { + increaseLayoutWidth(); + }); + $('#decreaseLayoutHeight').click(() => { + decreaseLayoutHeight(); + }); + $('#increaseLayoutHeight').click(() => { + increaseLayoutHeight(); + }); + $('#layoutResetNodes').click(() => { + layoutResetNodes(); + }); + $('#layoutTitleUp').click(() => { + layoutTitleUp(); + }); + $('#layoutTitleDown').click(() => { + layoutTitleDown(); + }); + $('#layoutTitleLeft').click(() => { + layoutTitleLeft(); + }); + $('#layoutTitleRight').click(() => { + layoutTitleRight(); + }); + $('#toggleLayoutTitle').click(() => { + toggleLayoutTitle(); + }); + $('#saveLayout').click(() => { + saveLayout(); + }); + $('#cancelLayout').click(() => { + cancelLayout(); + }); +} diff --git a/src/listeners.js b/src/listeners.js index 119714b..c9833e6 100644 --- a/src/listeners.js +++ b/src/listeners.js @@ -1,38 +1,86 @@ // Most Listeners are stored here -import { simulationArea } from "./simulationArea"; -import { scheduleUpdate, update, updateSelectionsAndPane } from "./engine"; -import { width,height } from "./circuit"; -import { changeScale } from "./canvasApi"; -import { scheduleBackup } from "./backupCircuit"; -import { hideProperties } from "./ux"; -export function startListeners() { - window.addEventListener('keyup', function (e) { +import { layoutModeGet } from './layoutMode'; +import simulationArea from './simulationArea'; +import { + scheduleUpdate, update, updateSelectionsAndPane, + wireToBeCheckedSet, updatePositionSet, updateSimulationSet, + updateCanvasSet, gridUpdateSet, errorDetectedSet, +} from './engine'; +import { changeScale } from './canvasApi'; +import { scheduleBackup } from './data/backupCircuit'; +import { hideProperties, deleteSelected, uxvar } from './ux'; +import { + updateRestrictedElementsList, updateRestrictedElementsInScope, hideRestricted, showRestricted, +} from './restrictedElementDiv'; +import { removeMiniMap, updatelastMinimapShown } from './minimap'; +import undo from './data/undo'; +import { copy, paste, selectAll } from './events'; +import save from './data/save'; +import { layoutUpdate } from './layoutMode'; + +const unit = 10; +let createNode = false; // Flag to create node when its value ==tru)e +export function createNodeSet(param) { + createNode = param; +} +export function createNodeGet(param) { + return createNode; +} + +let stopWire = true; // flag for stopoing making Nodes when the second terminal reaches a Node (closed path) +export function stopWireSet(param) { + stopWire = param; +} + + +export default function startListeners() { + $('#deleteSelected').click(() => { + deleteSelected(); + }); + + $('#zoomIn').click(() => { + changeScale(0.2, 'zoomButton', 'zoomButton', 2); + }); + + $('#zoomOut').click(() => { + changeScale(-0.2, 'zoomButton', 'zoomButton', 2); + }); + + $('#undoButton').click(() => { + undo(); + }); + + window.addEventListener('keyup', (e) => { scheduleUpdate(1); simulationArea.shiftDown = e.shiftKey; if (e.keyCode == 16) { simulationArea.shiftDown = false; } - if (e.key == "Meta" || e.key == "Control") { + if (e.key == 'Meta' || e.key == 'Control') { simulationArea.controlDown = false; } }); - document.getElementById("simulationArea").addEventListener('mousedown', function (e) { - $("input").blur(); - errorDetected = false; - updateSimulation = true; - updatePosition = true; - updateCanvas = true; + document.getElementById('simulationArea').addEventListener('mousedown', (e) => { + createNodeSet(true); + stopWireSet(false); + simulationArea.mouseDown = true; + + $('input').blur(); + + errorDetectedSet(false); + updateSimulationSet(true); + updatePositionSet(true); + updateCanvasSet(true); simulationArea.lastSelected = undefined; simulationArea.selected = false; simulationArea.hover = undefined; - var rect = simulationArea.canvas.getBoundingClientRect(); + const rect = simulationArea.canvas.getBoundingClientRect(); simulationArea.mouseDownRawX = (e.clientX - rect.left) * DPR; simulationArea.mouseDownRawY = (e.clientY - rect.top) * DPR; simulationArea.mouseDownX = Math.round(((simulationArea.mouseDownRawX - globalScope.ox) / globalScope.scale) / unit) * unit; simulationArea.mouseDownY = Math.round(((simulationArea.mouseDownRawY - globalScope.oy) / globalScope.scale) / unit) * unit; - simulationArea.mouseDown = true; simulationArea.oldx = globalScope.ox; simulationArea.oldy = globalScope.oy; @@ -41,7 +89,7 @@ export function startListeners() { scheduleUpdate(1); $('.dropdown.open').removeClass('open'); }); - document.getElementById("simulationArea").addEventListener('mouseup', function (e) { + document.getElementById('simulationArea').addEventListener('mouseup', (e) => { if (simulationArea.lastSelected) simulationArea.lastSelected.newElement = false; /* handling restricted circuit elements @@ -52,157 +100,190 @@ export function startListeners() { globalScope.restrictedCircuitElementsUsed.push(simulationArea.lastSelected.objectType); updateRestrictedElementsList(); } + + // deselect multible elements with click + if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.length > 0 + ) { + if ( + !simulationArea.multipleObjectSelections.includes( + simulationArea.lastSelected, + ) + ) { simulationArea.multipleObjectSelections = []; } + } }); window.addEventListener('mousemove', onMouseMove); - - window.addEventListener('keydown', function (e) { - + window.addEventListener('keydown', (e) => { + if (document.activeElement.tagName == 'INPUT') return; + if (listenToSimulator) { // If mouse is focusing on input element, then override any action // if($(':focus').length){ // return; // } - if (simulationArea.mouseRawX < 0 || simulationArea.mouseRawY < 0 || simulationArea.mouseRawX > width || simulationArea.mouseRawY > height) { - return; - } else { + if (document.activeElement.tagName == 'INPUT' || simulationArea.mouseRawX < 0 || simulationArea.mouseRawY < 0 || simulationArea.mouseRawX > width || simulationArea.mouseRawY > height) { + return; + } // HACK TO REMOVE FOCUS ON PROPERTIES - if (document.activeElement.type == "number") { + if (document.activeElement.type == 'number') { hideProperties(); - showProperties(simulationArea.lastSelected) + showProperties(simulationArea.lastSelected); } - } - errorDetected = false; - updateSimulation = true; - updatePosition = true; - simulationArea.shiftDown = e.shiftKey; - // zoom in (+) - if (e.key == "Meta" || e.key == "Control") { - simulationArea.controlDown = true; - } + errorDetectedSet(false); + updateSimulationSet(true); + updatePositionSet(true); + simulationArea.shiftDown = e.shiftKey; - if (simulationArea.controlDown && (e.keyCode == 187 || e.keyCode == 171)) { - e.preventDefault(); - if (globalScope.scale < 4 * DPR) { - changeScale(.1 * DPR); + // stop making wires when we connect the 2nd termial to a node + if (stopWire) { + createNodeSet(false); } - } - // zoom out (-) - if (simulationArea.controlDown && (e.keyCode == 189 || e.keyCode == 173)) { - e.preventDefault(); - if (globalScope.scale > 0.5 * DPR) { - changeScale(-.1 * DPR); + + if (e.key == 'Meta' || e.key == 'Control') { + simulationArea.controlDown = true; } - } - if (simulationArea.mouseRawX < 0 || simulationArea.mouseRawY < 0 || simulationArea.mouseRawX > width || simulationArea.mouseRawY > height) return; - scheduleUpdate(1); - updateCanvas = true; - wireToBeChecked = 1; + // zoom in (+) + if ((simulationArea.controlDown && (e.keyCode == 187 || e.keyCode == 171)) || e.keyCode == 107) { + e.preventDefault(); + ZoomIn(); + } + // zoom out (-) + if ((simulationArea.controlDown && (e.keyCode == 189 || e.keyCode == 173)) || e.keyCode == 109) { + e.preventDefault(); + ZoomOut(); + } - // Needs to be deprecated, moved to more recent listeners - if (simulationArea.controlDown && (e.key == "C" || e.key == "c")) { + if (simulationArea.mouseRawX < 0 || simulationArea.mouseRawY < 0 || simulationArea.mouseRawX > width || simulationArea.mouseRawY > height) return; + + scheduleUpdate(1); + updateCanvasSet(true); + wireToBeCheckedSet(1); + + // Needs to be deprecated, moved to more recent listeners + if (simulationArea.controlDown && (e.key == 'C' || e.key == 'c')) { // simulationArea.copyList=simulationArea.multipleObjectSelections.slice(); // if(simulationArea.lastSelected&&simulationArea.lastSelected!==simulationArea.root&&!simulationArea.copyList.contains(simulationArea.lastSelected)){ // simulationArea.copyList.push(simulationArea.lastSelected); // } // copy(simulationArea.copyList); - } - if (simulationArea.controlDown && (e.key == "V" || e.key == "v")) { - // paste(simulationArea.copyData); - } + } - if (simulationArea.lastSelected && simulationArea.lastSelected.keyDown) { - if (e.key.toString().length == 1 || e.key.toString() == "Backspace") { - simulationArea.lastSelected.keyDown(e.key.toString()); - return; + + if (simulationArea.lastSelected && simulationArea.lastSelected.keyDown) { + if (e.key.toString().length == 1 || e.key.toString() == 'Backspace') { + simulationArea.lastSelected.keyDown(e.key.toString()); + return; + } } - } + if (simulationArea.lastSelected && simulationArea.lastSelected.keyDown2) { + if (e.key.toString().length == 1) { + simulationArea.lastSelected.keyDown2(e.key.toString()); + return; + } + } - if (simulationArea.lastSelected && simulationArea.lastSelected.keyDown2) { - if (e.key.toString().length == 1) { - simulationArea.lastSelected.keyDown2(e.key.toString()); - return; + if (simulationArea.lastSelected && simulationArea.lastSelected.keyDown3) { + if (e.key.toString() != 'Backspace' && e.key.toString() != 'Delete') { + simulationArea.lastSelected.keyDown3(e.key.toString()); + return; + } } - } + if (e.keyCode == 16) { + simulationArea.shiftDown = true; + if (simulationArea.lastSelected && !simulationArea.lastSelected.keyDown && simulationArea.lastSelected.objectType != 'Wire' && simulationArea.lastSelected.objectType != 'CircuitElement' && !simulationArea.multipleObjectSelections.contains(simulationArea.lastSelected)) { + simulationArea.multipleObjectSelections.push(simulationArea.lastSelected); + } + } - if (simulationArea.lastSelected && simulationArea.lastSelected.keyDown3) { - if (e.key.toString() != "Backspace" && e.key.toString() != "Delete") { - simulationArea.lastSelected.keyDown3(e.key.toString()); - return; + if (e.keyCode == 8 || e.key == 'Delete') { + deleteSelected(); } - } + if (simulationArea.controlDown && e.key.charCodeAt(0) == 122) { // detect the special CTRL-Z code + undo(); + } - if (e.keyCode == 16) { - simulationArea.shiftDown = true; - if (simulationArea.lastSelected && !simulationArea.lastSelected.keyDown && simulationArea.lastSelected.objectType != "Wire" && simulationArea.lastSelected.objectType != "CircuitElement" && !simulationArea.multipleObjectSelections.contains(simulationArea.lastSelected)) { - simulationArea.multipleObjectSelections.push(simulationArea.lastSelected); + // Detect online save shortcut (CTRL+S) + if (simulationArea.controlDown && e.keyCode == 83 && !simulationArea.shiftDown) { + save(); + e.preventDefault(); + } + // Detect offline save shortcut (CTRL+SHIFT+S) + if (simulationArea.controlDown && e.keyCode == 83 && simulationArea.shiftDown) { + saveOffline(); + e.preventDefault(); } - } - if (e.keyCode == 8 || e.key == "Delete") { - delete_selected(); - } + // Detect Select all Shortcut + if (simulationArea.controlDown && (e.keyCode == 65 || e.keyCode == 97)) { + selectAll(); + e.preventDefault(); + } - if (simulationArea.controlDown && e.key.charCodeAt(0) == 122) { // detect the special CTRL-Z code - undo(); - } + // deselect all Shortcut + if (e.keyCode == 27) { + simulationArea.multipleObjectSelections = []; + simulationArea.lastSelected = undefined; + e.preventDefault(); + } - // Detect online save shortcut (CTRL+S) - if (simulationArea.controlDown && e.keyCode == 83 && !simulationArea.shiftDown) { - save(); - e.preventDefault(); - } - // Detect offline save shortcut (CTRL+SHIFT+S) - if (simulationArea.controlDown && e.keyCode == 83 && simulationArea.shiftDown) { - saveOffline(); - e.preventDefault(); - } + // change direction fns + if (simulationArea.lastSelected != undefined) { + let direction = ''; + switch (e.keyCode) { + case 37: + case 65: + direction = 'LEFT'; + break; + + case 38: + case 87: + direction = 'UP'; + break; + + case 39: + case 68: + direction = 'RIGHT'; + break; + + case 40: + case 83: + direction = 'DOWN'; + break; + + default: + break; + } + if (direction !== '') { + simulationArea.lastSelected.newDirection(direction); + } + } - // Detect Select all Shortcut - if (simulationArea.controlDown && (e.keyCode == 65 || e.keyCode == 97)) { - selectAll(); - e.preventDefault(); - } + if ((e.keyCode == 113 || e.keyCode == 81) && simulationArea.lastSelected != undefined) { + if (simulationArea.lastSelected.bitWidth !== undefined) { simulationArea.lastSelected.newBitWidth(parseInt(prompt('Enter new bitWidth'), 10)); } + } - //change direction fns - if ((e.keyCode == 37 || e.keyCode == 65) && simulationArea.lastSelected != undefined) { - simulationArea.lastSelected.newDirection("LEFT"); - } - if ((e.keyCode == 38 || e.keyCode == 87) && simulationArea.lastSelected != undefined) { - simulationArea.lastSelected.newDirection("UP"); - } - if ((e.keyCode == 39 || e.keyCode == 68) && simulationArea.lastSelected != undefined) { - simulationArea.lastSelected.newDirection("RIGHT"); - } - if ((e.keyCode == 40 || e.keyCode == 83) && simulationArea.lastSelected != undefined) { - simulationArea.lastSelected.newDirection("DOWN"); - } - if ((e.keyCode == 113 || e.keyCode == 81) && simulationArea.lastSelected != undefined) { - if (simulationArea.lastSelected.bitWidth !== undefined) - simulationArea.lastSelected.newBitWidth(parseInt(prompt("Enter new bitWidth"), 10)); - } - if (simulationArea.controlDown && (e.key == "T" || e.key == "t")) { + if (simulationArea.controlDown && (e.key == 'T' || e.key == 't')) { // e.preventDefault(); //browsers normally open a new tab - simulationArea.changeClockTime(prompt("Enter Time:")); - } - if ((e.keyCode == 108 || e.keyCode == 76) && simulationArea.lastSelected != undefined) { - if (simulationArea.lastSelected.setLabel !== undefined) { - var labl = prompt("Enter The Label : ", simulationArea.lastSelected.label); - if (labl) - simulationArea.lastSelected.setLabel(labl); + simulationArea.changeClockTime(prompt('Enter Time:')); + } + // f1 key for opening the documentation page + if (e.keyCode === 112) { + e.preventDefault(); + window.open('https://docs.circuitverse.org/', '_blank'); } } - }) + }); - document.getElementById("simulationArea").addEventListener('dblclick', function (e) { + + document.getElementById('simulationArea').addEventListener('dblclick', (e) => { scheduleUpdate(2); if (simulationArea.lastSelected && simulationArea.lastSelected.dblclick !== undefined) { simulationArea.lastSelected.dblclick(); @@ -214,102 +295,78 @@ export function startListeners() { window.addEventListener('mouseup', onMouseUp); + document.getElementById('simulationArea').addEventListener('mousewheel', MouseScroll); + document.getElementById('simulationArea').addEventListener('DOMMouseScroll', MouseScroll); + document.addEventListener('cut', (e) => { + if (document.activeElement.tagName == 'INPUT') return; - document.getElementById("simulationArea").addEventListener('mousewheel', MouseScroll); - document.getElementById("simulationArea").addEventListener('DOMMouseScroll', MouseScroll); + if (listenToSimulator) { + simulationArea.copyList = simulationArea.multipleObjectSelections.slice(); + if (simulationArea.lastSelected && simulationArea.lastSelected !== simulationArea.root && !simulationArea.copyList.contains(simulationArea.lastSelected)) { + simulationArea.copyList.push(simulationArea.lastSelected); + } - function MouseScroll(event) { - updateCanvas = true; - event.preventDefault() - var deltaY = event.wheelDelta ? event.wheelDelta : -event.detail; - var scrolledUp = deltaY < 0; - var scrolledDown = deltaY > 0; + const textToPutOnClipboard = copy(simulationArea.copyList, true); - if (event.ctrlKey) { - if (scrolledUp && globalScope.scale > 0.5 * DPR) { - changeScale(-.1 * DPR); - } - if (scrolledDown && globalScope.scale < 4 * DPR) { - changeScale(.1 * DPR); - } - } else { - if (scrolledUp && globalScope.scale < 4 * DPR) { - changeScale(.1 * DPR); - } - if (scrolledDown && globalScope.scale > 0.5 * DPR) { - changeScale(-.1 * DPR); + // Updated restricted elements + updateRestrictedElementsInScope(); + + localStorage.setItem('clipboardData', textToPutOnClipboard); + e.preventDefault(); + if (textToPutOnClipboard == undefined) return; + if (isIe) { + window.clipboardData.setData('Text', textToPutOnClipboard); + } else { + e.clipboardData.setData('text/plain', textToPutOnClipboard); } } + }); - updateCanvas = true; - gridUpdate = true; - if (layoutMode) layoutUpdate(); - else update(); // Schedule update not working, this is INEFFICIENT - } - - document.addEventListener('cut', function (e) { - simulationArea.copyList = simulationArea.multipleObjectSelections.slice(); - if (simulationArea.lastSelected && simulationArea.lastSelected !== simulationArea.root && !simulationArea.copyList.contains(simulationArea.lastSelected)) { - simulationArea.copyList.push(simulationArea.lastSelected); - } + document.addEventListener('copy', (e) => { + if (document.activeElement.tagName == 'INPUT') return; + if (listenToSimulator) { + simulationArea.copyList = simulationArea.multipleObjectSelections.slice(); + if (simulationArea.lastSelected && simulationArea.lastSelected !== simulationArea.root && !simulationArea.copyList.contains(simulationArea.lastSelected)) { + simulationArea.copyList.push(simulationArea.lastSelected); + } - var textToPutOnClipboard = copy(simulationArea.copyList, true); + const textToPutOnClipboard = copy(simulationArea.copyList); - // Updated restricted elements - updateRestrictedElementsInScope(); + // Updated restricted elements + updateRestrictedElementsInScope(); - localStorage.setItem('clipboardData', textToPutOnClipboard); - e.preventDefault(); - if (textToPutOnClipboard == undefined) - return; - if (isIe) { - window.clipboardData.setData('Text', textToPutOnClipboard); - } else { - e.clipboardData.setData('text/plain', textToPutOnClipboard); + localStorage.setItem('clipboardData', textToPutOnClipboard); + e.preventDefault(); + if (textToPutOnClipboard == undefined) return; + if (isIe) { + window.clipboardData.setData('Text', textToPutOnClipboard); + } else { + e.clipboardData.setData('text/plain', textToPutOnClipboard); + } } - }); - document.addEventListener('copy', function (e) { - simulationArea.copyList = simulationArea.multipleObjectSelections.slice(); - if (simulationArea.lastSelected && simulationArea.lastSelected !== simulationArea.root && !simulationArea.copyList.contains(simulationArea.lastSelected)) { - simulationArea.copyList.push(simulationArea.lastSelected); - } + document.addEventListener('paste', (e) => { + if (document.activeElement.tagName == 'INPUT') return; - var textToPutOnClipboard = copy(simulationArea.copyList); + if (listenToSimulator) { + let data; + if (isIe) { + data = window.clipboardData.getData('Text'); + } else { + data = e.clipboardData.getData('text/plain'); + } - // Updated restricted elements - updateRestrictedElementsInScope(); + paste(data); - localStorage.setItem('clipboardData', textToPutOnClipboard); - e.preventDefault(); - if (textToPutOnClipboard == undefined) - return; - if (isIe) { - window.clipboardData.setData('Text', textToPutOnClipboard); - } else { - e.clipboardData.setData('text/plain', textToPutOnClipboard); - } - - }); + // Updated restricted elements + updateRestrictedElementsInScope(); - document.addEventListener('paste', function (e) { - var data; - if (isIe) { - data = window.clipboardData.getData('Text'); - } else { - data = e.clipboardData.getData('text/plain'); + e.preventDefault(); } - - paste(data); - - // Updated restricted elements - updateRestrictedElementsInScope(); - - e.preventDefault(); }); restrictedElements.forEach((element) => { @@ -319,31 +376,15 @@ export function startListeners() { $(`#${element}`).mouseout(() => { hideRestricted(); - }) + }); }); } -var isIe = (navigator.userAgent.toLowerCase().indexOf("msie") != -1 || - navigator.userAgent.toLowerCase().indexOf("trident") != -1); - - -function removeMiniMap() { - if (lightMode) return; - - if (simulationArea.lastSelected == globalScope.root && simulationArea.mouseDown) return; - if (lastMiniMapShown + 2000 >= new Date().getTime()) { - setTimeout(removeMiniMap, lastMiniMapShown + 2000 - new Date().getTime()); - return; - } - $('#miniMap').fadeOut('fast'); - -} - +let isIe = (navigator.userAgent.toLowerCase().indexOf('msie') != -1 + || navigator.userAgent.toLowerCase().indexOf('trident') != -1); function onMouseMove(e) { - - var rect = simulationArea.canvas.getBoundingClientRect(); - // console.log(rect) + const rect = simulationArea.canvas.getBoundingClientRect(); simulationArea.mouseRawX = (e.clientX - rect.left) * DPR; simulationArea.mouseRawY = (e.clientY - rect.top) * DPR; simulationArea.mouseXf = (simulationArea.mouseRawX - globalScope.ox) / globalScope.scale; @@ -351,80 +392,111 @@ function onMouseMove(e) { simulationArea.mouseX = Math.round(simulationArea.mouseXf / unit) * unit; simulationArea.mouseY = Math.round(simulationArea.mouseYf / unit) * unit; - updateCanvas = true; + updateCanvasSet(true); if (simulationArea.lastSelected && (simulationArea.mouseDown || simulationArea.lastSelected.newElement)) { - updateCanvas = true; - var fn; + updateCanvasSet(true); + let fn; if (simulationArea.lastSelected == globalScope.root) { fn = function () { updateSelectionsAndPane(); - } + }; } else { fn = function () { - if (simulationArea.lastSelected) - simulationArea.lastSelected.update(); + if (simulationArea.lastSelected) { simulationArea.lastSelected.update(); } }; } scheduleUpdate(0, 20, fn); } else { scheduleUpdate(0, 200); } - - } function onMouseUp(e) { - + createNodeSet(simulationArea.controlDown); + simulationArea.mouseDown = false; if (!lightMode) { - lastMiniMapShown = new Date().getTime(); + updatelastMinimapShown(); setTimeout(removeMiniMap, 2000); } - errorDetected = false; - updateSimulation = true; - updatePosition = true; - updateCanvas = true; - gridUpdate = true; - wireToBeChecked = true; + errorDetectedSet(false); + updateSimulationSet(true); + updatePositionSet(true); + updateCanvasSet(true); + gridUpdateSet(true); + wireToBeCheckedSet(1); scheduleUpdate(1); simulationArea.mouseDown = false; - for (var i = 0; i < 2; i++) { - updatePosition = true; - wireToBeChecked = true; + for (let i = 0; i < 2; i++) { + updatePositionSet(true); + wireToBeCheckedSet(1); update(); } - errorDetected = false; - updateSimulation = true; - updatePosition = true; - updateCanvas = true; - gridUpdate = true; - wireToBeChecked = true; + errorDetectedSet(false); + updateSimulationSet(true); + updatePositionSet(true); + updateCanvasSet(true); + gridUpdateSet(true); + wireToBeCheckedSet(1); scheduleUpdate(1); - var rect = simulationArea.canvas.getBoundingClientRect(); + const rect = simulationArea.canvas.getBoundingClientRect(); if (!(simulationArea.mouseRawX < 0 || simulationArea.mouseRawY < 0 || simulationArea.mouseRawX > width || simulationArea.mouseRawY > height)) { - smartDropXX = simulationArea.mouseX + 100; //Math.round(((simulationArea.mouseRawX - globalScope.ox+100) / globalScope.scale) / unit) * unit; - smartDropYY = simulationArea.mouseY - 50; //Math.round(((simulationArea.mouseRawY - globalScope.oy+100) / globalScope.scale) / unit) * unit; + uxvar.smartDropXX = simulationArea.mouseX + 100; // Math.round(((simulationArea.mouseRawX - globalScope.ox+100) / globalScope.scale) / unit) * unit; + uxvar.smartDropYY = simulationArea.mouseY - 50; // Math.round(((simulationArea.mouseRawY - globalScope.oy+100) / globalScope.scale) / unit) * unit; } +} +function resizeTabs() { + const $windowsize = $('body').width(); + const $sideBarsize = $('.side').width(); + const $maxwidth = ($windowsize - $sideBarsize); + $('#tabsBar div').each(function (e) { + $(this).css({ 'max-width': $maxwidth - 30 }); + }); } -function delete_selected() { +window.addEventListener('resize', resizeTabs); +resizeTabs(); - $("input").blur(); - hideProperties(); - // console.log(simulationArea.lastSelected) - if (simulationArea.lastSelected && !(simulationArea.lastSelected.objectType == "Node" && simulationArea.lastSelected.type != 2)) simulationArea.lastSelected.delete(); - for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) { - if (!(simulationArea.multipleObjectSelections[i].objectType == "Node" && simulationArea.multipleObjectSelections[i].type != 2)) simulationArea.multipleObjectSelections[i].cleanDelete(); +$(() => { + $('[data-toggle="tooltip"]').tooltip(); +}); + +// direction is only 1 or -1 +function handleZoom(direction) { + if (globalScope.scale > 0.5 * DPR) { + changeScale(direction * 0.1 * DPR); + } else if (globalScope.scale < 4 * DPR) { + changeScale(direction * 0.1 * DPR); } - simulationArea.multipleObjectSelections = []; + gridUpdateSet(true); +} - // Updated restricted elements - // updateRestrictedElementsInScope(); +function ZoomIn() { + handleZoom(1); } + +function ZoomOut() { + handleZoom(-1); +} + +export function MouseScroll(event) { + updateCanvasSet(true); + event.preventDefault(); + let deltaY = event.wheelDelta ? event.wheelDelta : -event.detail; + event.preventDefault(); + deltaY = event.wheelDelta ? event.wheelDelta : -event.detail; + const direction = deltaY > 0 ? 1 : -1; + handleZoom(direction); + updateCanvasSet(true); + gridUpdateSet(true); + + if (layoutModeGet()) layoutUpdate(); + else update(); // Schedule update not working, this is INEFFICIENT +} \ No newline at end of file diff --git a/src/minimap.js b/src/minimap.js new file mode 100755 index 0000000..552bc63 --- /dev/null +++ b/src/minimap.js @@ -0,0 +1,117 @@ +import simulationArea from './simulationArea'; + +/** + * @type {Object} miniMapArea + * This object is used to draw the miniMap. + * @property {number} pageY + * @property {number} pageX + * @property {HTMLCanvasObject} canvas - the canvas object + * @property {function} setup - used to setup the parameters and dimensions + * @property {function} play - used to draw outline of minimap and call resolve + * @property {function} resolve - used to resolve all objects and draw them on minimap + * @property {function} clear - used to clear minimap + * @category minimap + */ +let miniMapArea; +export default miniMapArea = { + canvas: document.getElementById('miniMapArea'), + setup() { + if (lightMode) return; + this.canvas = document.getElementById('miniMapArea'); + this.pageHeight = height; // Math.round(((parseInt($("#simulationArea").height())))/ratio)*ratio-50; // -50 for tool bar? Check again + this.pageWidth = width; // Math.round(((parseInt($("#simulationArea").width())))/ratio)*ratio; + this.pageY = (this.pageHeight - globalScope.oy); + this.pageX = (this.pageWidth - globalScope.ox); + + if (simulationArea.minHeight != undefined) { this.minY = Math.min(simulationArea.minHeight, (-globalScope.oy) / globalScope.scale); } else { this.minY = (-globalScope.oy) / globalScope.scale; } + if (simulationArea.maxHeight != undefined) { this.maxY = Math.max(simulationArea.maxHeight, this.pageY / globalScope.scale); } else { this.maxY = this.pageY / globalScope.scale; } + if (simulationArea.minWidth != undefined) { this.minX = Math.min(simulationArea.minWidth, (-globalScope.ox) / globalScope.scale); } else { this.minX = (-globalScope.ox) / globalScope.scale; } + if (simulationArea.maxWidth != undefined) { this.maxX = Math.max(simulationArea.maxWidth, (this.pageX) / globalScope.scale); } else { this.maxX = (this.pageX) / globalScope.scale; } + + const h = this.maxY - this.minY; + const w = this.maxX - this.minX; + + const ratio = Math.min(250 / h, 250 / w); + if (h > w) { + this.canvas.height = 250.0; + this.canvas.width = (250.0 * w) / h; + } else { + this.canvas.width = 250.0; + this.canvas.height = (250.0 * h) / w; + } + + this.canvas.height += 5; + this.canvas.width += 5; + + document.getElementById('miniMap').style.height = this.canvas.height; + document.getElementById('miniMap').style.width = this.canvas.width; + this.ctx = this.canvas.getContext('2d'); + this.play(ratio); + }, + + play(ratio) { + if (lightMode) return; + + this.ctx.fillStyle = '#bbb'; + this.ctx.rect(0, 0, this.canvas.width, this.canvas.height); + this.ctx.fill(); + this.resolve(ratio); + }, + resolve(ratio) { + if (lightMode) return; + + this.ctx.fillStyle = '#ddd'; + this.ctx.beginPath(); + this.ctx.rect(2.5 + ((this.pageX - this.pageWidth) / globalScope.scale - this.minX) * ratio, 2.5 + ((this.pageY - this.pageHeight) / globalScope.scale - this.minY) * ratio, this.pageWidth * ratio / globalScope.scale, this.pageHeight * ratio / globalScope.scale); + this.ctx.fill(); + + // to show the area of current canvas + const lst = updateOrder; + this.ctx.strokeStyle = 'green'; + this.ctx.fillStyle = 'DarkGreen'; + for (let i = 0; i < lst.length; i++) { + if (lst[i] === 'wires') { + for (let j = 0; j < globalScope[lst[i]].length; j++) { + this.ctx.beginPath(); + this.ctx.moveTo(2.5 + (globalScope[lst[i]][j].node1.absX() - this.minX) * ratio, 2.5 + (globalScope[lst[i]][j].node1.absY() - this.minY) * ratio); + this.ctx.lineTo(2.5 + (globalScope[lst[i]][j].node2.absX() - this.minX) * ratio, 2.5 + (globalScope[lst[i]][j].node2.absY() - this.minY) * ratio); + this.ctx.stroke(); + } + } else if (lst[i] != 'nodes') { + // Don't include SquareRGBLed here; it has correct size. + let ledY = 0; + if (lst[i] == 'DigitalLed' || lst[i] == 'VariableLed' || lst[i] == 'RGBLed') { ledY = 20; } + + for (let j = 0; j < globalScope[lst[i]].length; j++) { + const xx = (globalScope[lst[i]][j].x - simulationArea.minWidth); + const yy = (globalScope[lst[i]][j].y - simulationArea.minHeight); + this.ctx.beginPath(); + const obj = globalScope[lst[i]][j]; + this.ctx.rect(2.5 + (obj.x - obj.leftDimensionX - this.minX) * ratio, 2.5 + (obj.y - obj.upDimensionY - this.minY) * ratio, (obj.rightDimensionX + obj.leftDimensionX) * ratio, (obj.downDimensionY + obj.upDimensionY + ledY) * ratio); + + this.ctx.fill(); + this.ctx.stroke(); + } + } + } + }, + clear() { + if (lightMode) return; + $('#miniMapArea').css('z-index', '-1'); + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + }, +}; +let lastMiniMapShown; +export function updatelastMinimapShown() { + lastMiniMapShown = new Date().getTime(); +} +export function removeMiniMap() { + if (lightMode) return; + + if (simulationArea.lastSelected == globalScope.root && simulationArea.mouseDown) return; + if (lastMiniMapShown + 2000 >= new Date().getTime()) { + setTimeout(removeMiniMap, lastMiniMapShown + 2000 - new Date().getTime()); + return; + } + $('#miniMap').fadeOut('fast'); +} diff --git a/src/module.js b/src/module.js deleted file mode 100644 index 5a6170a..0000000 --- a/src/module.js +++ /dev/null @@ -1,4033 +0,0 @@ -import CircuitElement from "./circuitElement" -import { Node, findNode } from "./node"; -import { simulationArea } from "./simulationArea"; -import { correctWidth,lineTo,moveTo,arc } from "./canvasApi"; - -function changeInputSize(size) { - if (size == undefined || size < 2 || size > 10) return; - if (this.inputSize == size) return; - size = parseInt(size, 10) - console.log(this.objectType, size) - var obj = new window[this.objectType](this.x, this.y, this.scope, this.direction, size, this.bitWidth); - simulationArea.lastSelected = obj; - this.delete(); - console.log("obj", obj) - return obj; - // showProperties(obj); -} - -export var moduleProperty = { "changeInputSize": changeInputSize } - - -export class AndGate extends CircuitElement { - constructor(x, y, scope = globalScope, dir = "RIGHT", inputLength = 2, bitWidth = 1) { - super(x, y, scope , dir, bitWidth) - console.log(this) - this.rectangleObject = false; - this.setDimensions(15, 20); - this.inp = []; - - this.inputSize = inputLength; - - - //variable inputLength , node creation - if (inputLength % 2 == 1) { - for (var i = 0; i < inputLength / 2 - 1; i++) { - var a = new Node(-10, -10 * (i + 1), 0, this); - this.inp.push(a); - } - var a = new Node(-10, 0, 0, this); - this.inp.push(a); - for (var i = inputLength / 2 + 1; i < inputLength; i++) { - var a = new Node(-10, 10 * (i + 1 - inputLength / 2 - 1), 0, this); - this.inp.push(a); - } - } else { - for (var i = 0; i < inputLength / 2; i++) { - var a = new Node(-10, -10 * (i + 1), 0, this); - this.inp.push(a); - } - for (var i = inputLength / 2; i < inputLength; i++) { - var a = new Node(-10, 10 * (i + 1 - inputLength / 2), 0, this); - this.inp.push(a); - } - } - - this.output1 = new Node(20, 0, 1, this); - - - } - - //fn to create save Json Data of object - customSave() { - var data = { - constructorParamaters: [this.direction, this.inputSize, this.bitWidth], - nodes: { - inp: this.inp.map(findNode), - output1: findNode(this.output1) - }, - - } - return data; - } - - //resolve output values based on inputData - resolve() { - var result = this.inp[0].value || 0; - if (this.isResolvable() == false) { - return; - } - for (var i = 1; i < this.inputSize; i++) - result = result & ((this.inp[i].value) || 0); - this.output1.value = result; - simulationArea.simulationQueue.add(this.output1); - } - - //fn to draw - customDraw() { - - let ctx = simulationArea.context; - - ctx.beginPath(); - ctx.lineWidth = correctWidth(3); - ctx.strokeStyle = "black"; //("rgba(0,0,0,1)"); - ctx.fillStyle = "white"; - var xx = this.x; - var yy = this.y; - - moveTo(ctx,-10, -20, xx, yy, this.direction); - lineTo(ctx,0, -20, xx, yy, this.direction); - arc(ctx,0, 0, 20, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); - lineTo(ctx, -10, 20, xx, yy, this.direction); - lineTo(ctx,-10, -20, xx, yy, this.direction); - ctx.closePath(); - - // if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; - ctx.fill(); - ctx.stroke(); - - } -} - -AndGate.prototype.tooltipText = "And Gate Tooltip : Implements logical conjunction"; -AndGate.prototype.alwaysResolve = true; -AndGate.prototype.verilogType = "and"; -AndGate.prototype.changeInputSize = changeInputSize; - -// class NandGate extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", inputLength = 2, bitWidth = 1) { -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.setDimensions(15, 20); -// this.inp = []; - - -// this.inputSize = inputLength; - - -// //variable inputLength , node creation -// if (inputLength % 2 == 1) { -// for (var i = 0; i < inputLength / 2 - 1; i++) { -// var a = new Node(-10, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// var a = new Node(-10, 0, 0, this); -// this.inp.push(a); -// for (var i = inputLength / 2 + 1; i < inputLength; i++) { -// var a = new Node(-10, 10 * (i + 1 - inputLength / 2 - 1), 0, this); -// this.inp.push(a); -// } -// } else { -// for (var i = 0; i < inputLength / 2; i++) { -// var a = new Node(-10, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// for (var i = inputLength / 2; i < inputLength; i++) { -// var a = new Node(-10, 10 * (i + 1 - inputLength / 2), 0, this); -// this.inp.push(a); -// } -// } - -// this.output1 = new Node(30, 0, 1, this); - - -// } - -// //fn to create save Json Data of object -// customSave() { -// var data = { - -// constructorParamaters: [this.direction, this.inputSize, this.bitWidth], -// nodes: { -// inp: this.inp.map(findNode), -// output1: findNode(this.output1) -// }, -// } -// return data; -// } - -// //resolve output values based on inputData -// resolve() { -// var result = this.inp[0].value || 0; -// if (this.isResolvable() == false) { -// return; -// } -// for (var i = 1; i < this.inputSize; i++) -// result = result & (this.inp[i].value || 0); -// result = ((~result >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); -// this.output1.value = result; -// simulationArea.simulationQueue.add(this.output1); -// } - -// //fn to draw -// customDraw() { - -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.lineWidth = correctWidth(3); -// ctx.strokeStyle = "black"; -// ctx.fillStyle = "white"; -// var xx = this.x; -// var yy = this.y; - -// moveTo(ctx, -10, -20, xx, yy, this.direction); -// lineTo(ctx, 0, -20, xx, yy, this.direction); -// arc(ctx, 0, 0, 20, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); -// lineTo(ctx, -10, 20, xx, yy, this.direction); -// lineTo(ctx, -10, -20, xx, yy, this.direction); -// ctx.closePath(); - -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.5)"; -// ctx.fill(); -// ctx.stroke(); -// ctx.beginPath(); -// drawCircle2(ctx, 25, 0, 5, xx, yy, this.direction); -// ctx.stroke(); - - -// } -// } - -// NandGate.prototype.tooltipText = "Nand Gate ToolTip : Combination of AND and NOT gates"; -// NandGate.prototype.alwaysResolve = true; -// NandGate.prototype.changeInputSize = changeInputSize; -// NandGate.prototype.verilogType = "nand"; - -// class Multiplexer extends CircuitElement { -// constructor( -// x, -// y, -// scope = globalScope, -// dir = "RIGHT", -// bitWidth = 1, -// controlSignalSize = 1 -// ) { -// // //console.log("HIT"); -// // //console.log(x,y,scope,dir,bitWidth,controlSignalSize); -// super(x, y, scope, dir, bitWidth); - -// this.controlSignalSize = controlSignalSize || parseInt(prompt("Enter control signal bitWidth"), 10); -// this.inputSize = 1 << this.controlSignalSize; -// this.xOff = 0; -// this.yOff = 1; -// if (this.controlSignalSize == 1) { -// this.xOff = 10; -// } -// if (this.controlSignalSize <= 3) { -// this.yOff = 2; -// } - -// this.setDimensions(20 - this.xOff, this.yOff * 5 * (this.inputSize)); -// this.rectangleObject = false; - -// this.inp = []; -// for (var i = 0; i < this.inputSize; i++) { -// var a = new Node(-20 + this.xOff, +this.yOff * 10 * (i - this.inputSize / 2) + 10, 0, this); -// this.inp.push(a); -// } - -// this.output1 = new Node(20 - this.xOff, 0, 1, this); -// this.controlSignalInput = new Node(0, this.yOff * 10 * (this.inputSize / 2 - 1) + this.xOff + 10, 0, this, this.controlSignalSize, "Control Signal"); - - - -// } - -// changeControlSignalSize(size) { -// if (size == undefined || size < 1 || size > 32) return; -// if (this.controlSignalSize == size) return; -// var obj = new window[this.objectType](this.x, this.y, this.scope, this.direction, this.bitWidth, size); -// this.cleanDelete(); -// simulationArea.lastSelected = obj; -// return obj; -// } - -// newBitWidth(bitWidth) { -// this.bitWidth = bitWidth; -// for (var i = 0; i < this.inputSize; i++) { -// this.inp[i].bitWidth = bitWidth -// } -// this.output1.bitWidth = bitWidth; -// } - -// //fn to create save Json Data of object -// isResolvable() { -// if (this.controlSignalInput.value != undefined && this.inp[this.controlSignalInput.value].value != undefined) return true; -// return false; -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth, this.controlSignalSize], -// nodes: { -// inp: this.inp.map(findNode), -// output1: findNode(this.output1), -// controlSignalInput: findNode(this.controlSignalInput) -// }, -// } -// return data; -// } - -// resolve() { - -// if (this.isResolvable() == false) { -// return; -// } -// this.output1.value = this.inp[this.controlSignalInput.value].value; -// simulationArea.simulationQueue.add(this.output1); -// } - -// customDraw() { - -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// ctx.beginPath(); -// moveTo(ctx, 0, this.yOff * 10 * (this.inputSize / 2 - 1) + 10 + 0.5 * this.xOff, xx, yy, this.direction); -// lineTo(ctx, 0, this.yOff * 5 * (this.inputSize - 1) + this.xOff, xx, yy, this.direction); -// ctx.stroke(); - -// ctx.lineWidth = correctWidth(3); -// ctx.beginPath(); -// ctx.strokeStyle = ("rgba(0,0,0,1)"); - -// ctx.fillStyle = "white"; -// moveTo(ctx, -20 + this.xOff, -this.yOff * 10 * (this.inputSize / 2), xx, yy, this.direction); -// lineTo(ctx, -20 + this.xOff, 20 + this.yOff * 10 * (this.inputSize / 2 - 1), xx, yy, this.direction); -// lineTo(ctx, 20 - this.xOff, +this.yOff * 10 * (this.inputSize / 2 - 1) + this.xOff, xx, yy, this.direction); -// lineTo(ctx, 20 - this.xOff, -this.yOff * 10 * (this.inputSize / 2) - this.xOff + 20, xx, yy, this.direction); - -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) -// ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - - - -// ctx.beginPath(); -// // ctx.lineWidth = correctWidth(2); -// ctx.fillStyle = "black"; -// ctx.textAlign = "center"; -// for (var i = 0; i < this.inputSize; i++) { - -// if (this.direction == "RIGHT") fillText(ctx, String(i), xx + this.inp[i].x + 7, yy + this.inp[i].y + 2, 10); -// else if (this.direction == "LEFT") fillText(ctx, String(i), xx + this.inp[i].x - 7, yy + this.inp[i].y + 2, 10); -// else if (this.direction == "UP") fillText(ctx, String(i), xx + this.inp[i].x, yy + this.inp[i].y - 4, 10); -// else fillText(ctx, String(i), xx + this.inp[i].x, yy + this.inp[i].y + 10, 10); -// } -// ctx.fill(); -// } -// } - -// Multiplexer.prototype.tooltipText = "Multiplexer ToolTip : Multiple inputs and a single line output."; -// Multiplexer.prototype.mutableProperties = { -// "controlSignalSize": { -// name: "Control Signal Size", -// type: "number", -// max: "32", -// min: "1", -// func: "changeControlSignalSize", -// }, -// } - -// class XorGate extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", inputs = 2, bitWidth = 1) { -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.setDimensions(15, 20); - -// this.inp = []; -// this.inputSize = inputs; - -// if (inputs % 2 == 1) { -// for (var i = 0; i < inputs / 2 - 1; i++) { -// var a = new Node(-20, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// var a = new Node(-20, 0, 0, this); -// this.inp.push(a); -// for (var i = inputs / 2 + 1; i < inputs; i++) { -// var a = new Node(-20, 10 * (i + 1 - inputs / 2 - 1), 0, this); -// this.inp.push(a); -// } -// } else { -// for (var i = 0; i < inputs / 2; i++) { -// var a = new Node(-20, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// for (var i = inputs / 2; i < inputs; i++) { -// var a = new Node(-20, 10 * (i + 1 - inputs / 2), 0, this); -// this.inp.push(a); -// } -// } -// this.output1 = new Node(20, 0, 1, this); - -// } - -// customSave() { -// // //console.log(this.scope.allNodes); -// var data = { -// constructorParamaters: [this.direction, this.inputSize, this.bitWidth], -// nodes: { -// inp: this.inp.map(findNode), -// output1: findNode(this.output1) -// }, -// } -// return data; -// } - -// resolve() { -// var result = this.inp[0].value || 0; -// if (this.isResolvable() == false) { -// return; -// } -// for (var i = 1; i < this.inputSize; i++) -// result = result ^ (this.inp[i].value || 0); - -// this.output1.value = result; -// simulationArea.simulationQueue.add(this.output1); -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; -// ctx.beginPath(); -// ctx.fillStyle = "white"; -// moveTo(ctx, -10, -20, xx, yy, this.direction, true); -// bezierCurveTo(0, -20, +15, -10, 20, 0, xx, yy, this.direction); -// bezierCurveTo(0 + 15, 0 + 10, 0, 0 + 20, -10, +20, xx, yy, this.direction); -// bezierCurveTo(0, 0, 0, 0, -10, -20, xx, yy, this.direction); -// // arc(ctx, 0, 0, -20, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); -// ctx.beginPath(); -// arc2(ctx, -35, 0, 25, 1.70 * (Math.PI), 0.30 * (Math.PI), xx, yy, this.direction); -// ctx.stroke(); - - -// } -// } - -// XorGate.prototype.tooltipText = "Xor Gate Tooltip : Implements an exclusive OR."; -// XorGate.prototype.alwaysResolve = true; - -// XorGate.prototype.changeInputSize = changeInputSize; -// XorGate.prototype.verilogType = "xor"; - -// class XnorGate extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", inputs = 2, bitWidth = 1) { -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.setDimensions(15, 20); - -// this.inp = []; -// this.inputSize = inputs; - -// if (inputs % 2 == 1) { -// for (var i = 0; i < inputs / 2 - 1; i++) { -// var a = new Node(-20, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// var a = new Node(-20, 0, 0, this); -// this.inp.push(a); -// for (var i = inputs / 2 + 1; i < inputs; i++) { -// var a = new Node(-20, 10 * (i + 1 - inputs / 2 - 1), 0, this); -// this.inp.push(a); -// } -// } else { -// for (var i = 0; i < inputs / 2; i++) { -// var a = new Node(-20, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// for (var i = inputs / 2; i < inputs; i++) { -// var a = new Node(-20, 10 * (i + 1 - inputs / 2), 0, this); -// this.inp.push(a); -// } -// } -// this.output1 = new Node(30, 0, 1, this); - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.inputSize, this.bitWidth], -// nodes: { -// inp: this.inp.map(findNode), -// output1: findNode(this.output1) -// }, -// } -// return data; -// } - -// resolve() { -// var result = this.inp[0].value || 0; -// if (this.isResolvable() == false) { -// return; -// } -// for (var i = 1; i < this.inputSize; i++) -// result = result ^ (this.inp[i].value || 0); -// result = ((~result >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); -// this.output1.value = result; -// simulationArea.simulationQueue.add(this.output1); -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; -// ctx.beginPath(); -// ctx.fillStyle = "white"; -// moveTo(ctx, -10, -20, xx, yy, this.direction, true); -// bezierCurveTo(0, -20, +15, -10, 20, 0, xx, yy, this.direction); -// bezierCurveTo(0 + 15, 0 + 10, 0, 0 + 20, -10, +20, xx, yy, this.direction); -// bezierCurveTo(0, 0, 0, 0, -10, -20, xx, yy, this.direction); -// // arc(ctx, 0, 0, -20, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); -// ctx.beginPath(); -// arc2(ctx, -35, 0, 25, 1.70 * (Math.PI), 0.30 * (Math.PI), xx, yy, this.direction); -// ctx.stroke(); -// ctx.beginPath(); -// drawCircle2(ctx, 25, 0, 5, xx, yy, this.direction); -// ctx.stroke(); - -// } -// } - -// XnorGate.prototype.alwaysResolve = true; -// XnorGate.prototype.tooltipText = "Xnor Gate ToolTip : Logical complement of the XOR gate"; -// XnorGate.prototype.changeInputSize = changeInputSize; -// XnorGate.prototype.verilogType = "xnor"; - -// class SevenSegDisplay extends CircuitElement { -// constructor(x, y, scope = globalScope) { -// super(x, y, scope, "RIGHT", 1); -// this.fixedBitWidth = true; -// this.directionFixed = true; -// this.setDimensions(30, 50); - -// this.g = new Node(-20, -50, 0, this); -// this.f = new Node(-10, -50, 0, this); -// this.a = new Node(+10, -50, 0, this); -// this.b = new Node(+20, -50, 0, this); -// this.e = new Node(-20, +50, 0, this); -// this.d = new Node(-10, +50, 0, this); -// this.c = new Node(+10, +50, 0, this); -// this.dot = new Node(+20, +50, 0, this); -// this.direction = "RIGHT"; - - -// } - -// customSave() { -// var data = { - -// nodes: { -// g: findNode(this.g), -// f: findNode(this.f), -// a: findNode(this.a), -// b: findNode(this.b), -// d: findNode(this.d), -// e: findNode(this.e), -// c: findNode(this.c), -// d: findNode(this.d), -// dot: findNode(this.dot) -// }, -// } -// return data; -// } - -// customDrawSegment(x1, y1, x2, y2, color) { -// if (color == undefined) color = "lightgrey"; -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = color; -// ctx.lineWidth = correctWidth(5); -// xx = this.x; -// yy = this.y; -// moveTo(ctx, x1, y1, xx, yy, this.direction); -// lineTo(ctx, x2, y2, xx, yy, this.direction); -// ctx.closePath(); -// ctx.stroke(); -// } - -// customDraw() { -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// this.customDrawSegment(18, -3, 18, -38, ["lightgrey", "red"][this.b.value]); -// this.customDrawSegment(18, 3, 18, 38, ["lightgrey", "red"][this.c.value]); -// this.customDrawSegment(-18, -3, -18, -38, ["lightgrey", "red"][this.f.value]); -// this.customDrawSegment(-18, 3, -18, 38, ["lightgrey", "red"][this.e.value]); -// this.customDrawSegment(-17, -38, 17, -38, ["lightgrey", "red"][this.a.value]); -// this.customDrawSegment(-17, 0, 17, 0, ["lightgrey", "red"][this.g.value]); -// this.customDrawSegment(-15, 38, 17, 38, ["lightgrey", "red"][this.d.value]); - -// ctx.beginPath(); -// var dotColor = ["lightgrey", "red"][this.dot.value] || "lightgrey" -// ctx.strokeStyle = dotColor; -// rect(ctx, xx + 22, yy + 42, 2, 2); -// ctx.stroke(); -// } -// } - -// SevenSegDisplay.prototype.tooltipText = "Seven Display ToolTip: Consists of 7+1 single bit inputs." - -// class SixteenSegDisplay extends CircuitElement { -// constructor(x, y, scope = globalScope) { -// super(x, y, scope, "RIGHT", 16); -// this.fixedBitWidth = true; -// this.directionFixed = true; -// this.setDimensions(30, 50); - -// this.input1 = new Node(0, -50, 0, this, 16); -// this.dot = new Node(0, 50, 0, this, 1); -// this.direction = "RIGHT"; -// } - -// customSave() { -// var data = { -// nodes: { -// input1: findNode(this.input1), -// dot: findNode(this.dot) -// } -// } -// return data; -// } - -// customDrawSegment(x1, y1, x2, y2, color) { -// if (color == undefined) color = "lightgrey"; -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = color; -// ctx.lineWidth = correctWidth(4); -// xx = this.x; -// yy = this.y; -// moveTo(ctx, x1, y1, xx, yy, this.direction); -// lineTo(ctx, x2, y2, xx, yy, this.direction); -// ctx.closePath(); -// ctx.stroke(); -// } - -// customDrawSegmentSlant(x1, y1, x2, y2, color) { -// if (color == undefined) color = "lightgrey"; -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = color; -// ctx.lineWidth = correctWidth(3); -// xx = this.x; -// yy = this.y; -// moveTo(ctx, x1, y1, xx, yy, this.direction); -// lineTo(ctx, x2, y2, xx, yy, this.direction); -// ctx.closePath(); -// ctx.stroke(); -// } - -// customDraw() { -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// var color = ["lightgrey", "red"]; -// var value = this.input1.value; - -// this.customDrawSegment(-20, -38, 0, -38, ["lightgrey", "red"][(value >> 15) & 1]); //a1 -// this.customDrawSegment(20, -38, 0, -38, ["lightgrey", "red"][(value >> 14) & 1]); //a2 -// this.customDrawSegment(21.5, -2, 21.5, -36, ["lightgrey", "red"][(value >> 13) & 1]); //b -// this.customDrawSegment(21.5, 2, 21.5, 36, ["lightgrey", "red"][(value >> 12) & 1]); //c -// this.customDrawSegment(-20, 38, 0, 38, ["lightgrey", "red"][(value >> 11) & 1]); //d1 -// this.customDrawSegment(20, 38, 0, 38, ["lightgrey", "red"][(value >> 10) & 1]); //d2 -// this.customDrawSegment(-21.5, 2, -21.5, 36, ["lightgrey", "red"][(value >> 9) & 1]); //e -// this.customDrawSegment(-21.5, -36, -21.5, -2, ["lightgrey", "red"][(value >> 8) & 1]); //f -// this.customDrawSegment(-20, 0, 0, 0, ["lightgrey", "red"][(value >> 7) & 1]); //g1 -// this.customDrawSegment(20, 0, 0, 0, ["lightgrey", "red"][(value >> 6) & 1]); //g2 -// this.customDrawSegmentSlant(0, 0, -21, -37, ["lightgrey", "red"][(value >> 5) & 1]); //h -// this.customDrawSegment(0, -2, 0, -36, ["lightgrey", "red"][(value >> 4) & 1]); //i -// this.customDrawSegmentSlant(0, 0, 21, -37, ["lightgrey", "red"][(value >> 3) & 1]); //j -// this.customDrawSegmentSlant(0, 0, 21, 37, ["lightgrey", "red"][(value >> 2) & 1]); //k -// this.customDrawSegment(0, 2, 0, 36, ["lightgrey", "red"][(value >> 1) & 1]); //l -// this.customDrawSegmentSlant(0, 0, -21, 37, ["lightgrey", "red"][(value >> 0) & 1]); //m - -// ctx.beginPath(); -// var dotColor = ["lightgrey", "red"][this.dot.value] || "lightgrey" -// ctx.strokeStyle = dotColor; -// rect(ctx, xx + 22, yy + 42, 2, 2); -// ctx.stroke(); -// } -// } - -// SixteenSegDisplay.prototype.tooltipText = "Sixteen Display ToolTip: Consists of 16+1 bit inputs."; - -// class HexDisplay extends CircuitElement { -// constructor(x, y, scope = globalScope) { -// super(x, y, scope, "RIGHT", 4); -// this.directionFixed = true; -// this.fixedBitWidth = true; -// this.setDimensions(30, 50); - -// this.inp = new Node(0, -50, 0, this, 4); -// this.direction = "RIGHT"; -// } - -// customSave() { -// var data = { - - -// nodes: { -// inp: findNode(this.inp) -// } - -// } -// return data; -// } - -// customDrawSegment(x1, y1, x2, y2, color) { -// if (color == undefined) color = "lightgrey"; -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = color; -// ctx.lineWidth = correctWidth(5); -// xx = this.x; -// yy = this.y; - -// moveTo(ctx, x1, y1, xx, yy, this.direction); -// lineTo(ctx, x2, y2, xx, yy, this.direction); -// ctx.closePath(); -// ctx.stroke(); -// } - -// customDraw() { -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// ctx.strokeStyle = "black"; -// ctx.lineWidth = correctWidth(3); -// var a = b = c = d = e = f = g = 0; -// switch (this.inp.value) { -// case 0: -// a = b = c = d = e = f = 1; -// break; -// case 1: -// b = c = 1; -// break; -// case 2: -// a = b = g = e = d = 1; -// break; -// case 3: -// a = b = g = c = d = 1; -// break; -// case 4: -// f = g = b = c = 1; -// break; -// case 5: -// a = f = g = c = d = 1; -// break; -// case 6: -// a = f = g = e = c = d = 1; -// break; -// case 7: -// a = b = c = 1; -// break; -// case 8: -// a = b = c = d = e = g = f = 1; -// break; -// case 9: -// a = f = g = b = c = 1; -// break; -// case 0xA: -// a = f = b = c = g = e = 1; -// break; -// case 0xB: -// f = e = g = c = d = 1; -// break; -// case 0xC: -// a = f = e = d = 1; -// break; -// case 0xD: -// b = c = g = e = d = 1; -// break; -// case 0xE: -// a = f = g = e = d = 1; -// break; -// case 0xF: -// a = f = g = e = 1; -// break; -// default: - -// } -// this.customDrawSegment(18, -3, 18, -38, ["lightgrey", "red"][b]); -// this.customDrawSegment(18, 3, 18, 38, ["lightgrey", "red"][c]); -// this.customDrawSegment(-18, -3, -18, -38, ["lightgrey", "red"][f]); -// this.customDrawSegment(-18, 3, -18, 38, ["lightgrey", "red"][e]); -// this.customDrawSegment(-17, -38, 17, -38, ["lightgrey", "red"][a]); -// this.customDrawSegment(-17, 0, 17, 0, ["lightgrey", "red"][g]); -// this.customDrawSegment(-15, 38, 17, 38, ["lightgrey", "red"][d]); - -// } -// } - -// HexDisplay.prototype.tooltipText = "Hex Display ToolTip: Inputs a 4 Bit Hex number and displays it." - -// class OrGate extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", inputs = 2, bitWidth = 1) { -// // Calling base class constructor -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.setDimensions(15, 20); -// // Inherit base class prototype - -// this.inp = []; -// this.inputSize = inputs; - - -// if (inputs % 2 == 1) { -// // for (var i = 0; i < inputs / 2 - 1; i++) { -// // var a = new Node(-10, -10 * (i + 1), 0, this); -// // this.inp.push(a); -// // } -// // var a = new Node(-10, 0, 0, this); -// // this.inp.push(a); -// // for (var i = inputs / 2 + 1; i < inputs; i++) { -// // var a = new Node(-10, 10 * (i + 1 - inputs / 2 - 1), 0, this); -// // this.inp.push(a); -// // } -// for (var i = Math.floor(inputs / 2) - 1; i >= 0; i--) { -// var a = new Node(-10, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// var a = new Node(-10, 0, 0, this); -// this.inp.push(a); -// for (var i = 0; i < Math.floor(inputs / 2); i++) { -// var a = new Node(-10, 10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// } else { -// for (var i = inputs / 2 - 1; i >= 0; i--) { -// var a = new Node(-10, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// for (var i = 0; i < inputs / 2; i++) { -// var a = new Node(-10, 10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// } -// this.output1 = new Node(20, 0, 1, this); - - - -// } - -// customSave() { -// var data = { - -// constructorParamaters: [this.direction, this.inputSize, this.bitWidth], - -// nodes: { -// inp: this.inp.map(findNode), -// output1: findNode(this.output1), -// }, -// } -// return data; -// } - -// resolve() { -// var result = this.inp[0].value || 0; -// if (this.isResolvable() == false) { -// return; -// } -// for (var i = 1; i < this.inputSize; i++) -// result = result | (this.inp[i].value || 0); -// this.output1.value = result; -// simulationArea.simulationQueue.add(this.output1); -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; -// ctx.beginPath(); -// ctx.fillStyle = "white"; - -// moveTo(ctx, -10, -20, xx, yy, this.direction, true); -// bezierCurveTo(0, -20, +15, -10, 20, 0, xx, yy, this.direction); -// bezierCurveTo(0 + 15, 0 + 10, 0, 0 + 20, -10, +20, xx, yy, this.direction); -// bezierCurveTo(0, 0, 0, 0, -10, -20, xx, yy, this.direction); -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - - - -// } -// } - -// OrGate.prototype.tooltipText = "Or Gate Tooltip : Implements logical disjunction"; -// OrGate.prototype.changeInputSize = changeInputSize; -// OrGate.prototype.alwaysResolve = true; -// OrGate.prototype.verilogType = "or"; - -// class Stepper extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT") { - -// super(x, y, scope, dir, 8); -// this.setDimensions(20, 20); - -// this.output1 = new Node(20, 0, 1, this, 8); -// this.state = 0; - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction], -// nodes: { -// output1: findNode(this.output1), -// }, -// values: { -// state: this.state -// } -// } -// return data; -// } - -// customDraw() { -// ctx = simulationArea.context; - -// ctx.beginPath(); -// ctx.font = "20px Georgia"; -// ctx.fillStyle = "green"; -// ctx.textAlign = "center"; -// fillText(ctx, this.state.toString(16), this.x, this.y + 5); -// ctx.fill();; -// } - -// resolve() { -// this.state = Math.min(this.state, (1 << this.bitWidth) - 1); -// this.output1.value = this.state; -// simulationArea.simulationQueue.add(this.output1); -// } - -// keyDown2(key) { -// //console.log(key); -// if (this.state < (1 << this.bitWidth) && (key == "+" || key == "=")) this.state++; -// if (this.state > 0 && (key == "_" || key == "-")) this.state--; -// } -// } - -// Stepper.prototype.tooltipText = "Stepper ToolTip: Increase/Decrease value by selecting the stepper and using +/- keys." - -// class NotGate extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { - -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.setDimensions(15, 15); - -// this.inp1 = new Node(-10, 0, 0, this); -// this.output1 = new Node(20, 0, 1, this); - - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth], -// nodes: { -// output1: findNode(this.output1), -// inp1: findNode(this.inp1) -// }, -// } -// return data; -// } - -// resolve() { -// if (this.isResolvable() == false) { -// return; -// } -// this.output1.value = ((~this.inp1.value >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); -// simulationArea.simulationQueue.add(this.output1); -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.strokeStyle = "black"; -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; -// ctx.beginPath(); -// ctx.fillStyle = "white"; -// moveTo(ctx, -10, -10, xx, yy, this.direction); -// lineTo(ctx, 10, 0, xx, yy, this.direction); -// lineTo(ctx, -10, 10, xx, yy, this.direction); -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); -// ctx.beginPath(); -// drawCircle2(ctx, 15, 0, 5, xx, yy, this.direction); -// ctx.stroke(); - -// } -// } - -// NotGate.prototype.tooltipText = "Not Gate Tooltip : Inverts the input digital signal."; - -// class ForceGate extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { - -// super(x, y, scope, dir, bitWidth); -// this.setDimensions(20, 10); - -// this.inp1 = new Node(-20, 0, 0, this); -// this.inp2 = new Node(0, 0, 0, this); -// this.output1 = new Node(20, 0, 1, this); - - -// } - -// isResolvable() { -// return (this.inp1.value != undefined || this.inp2.value != undefined) -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth], -// nodes: { -// output1: findNode(this.output1), -// inp1: findNode(this.inp1), -// inp2: findNode(this.inp2) -// }, -// } -// return data; -// } - -// resolve() { -// if (this.inp2.value != undefined) -// this.output1.value = this.inp2.value; -// else -// this.output1.value = this.inp1.value; -// simulationArea.simulationQueue.add(this.output1); -// } - -// customDraw() { - -// ctx = simulationArea.context; -// var xx = this.x; -// var yy = this.y; - - - -// ctx.beginPath(); -// ctx.fillStyle = "Black"; -// ctx.textAlign = "center" - -// fillText4(ctx, "I", -10, 0, xx, yy, this.direction, 10); -// fillText4(ctx, "O", 10, 0, xx, yy, this.direction, 10); -// ctx.fill(); - -// } -// } - -// ForceGate.prototype.tooltipText = "Force Gate ToolTip : ForceGate Selected."; - -// class Text extends CircuitElement { -// constructor(x, y, scope = globalScope, label = "") { - -// super(x, y, scope, "RIGHT", 1); -// // this.setDimensions(15, 15); -// this.fixedBitWidth = true; -// this.directionFixed = true; -// this.labelDirectionFixed = true; -// this.setHeight(10); -// this.setLabel(label); - - - -// } - -// setLabel(str = "") { - -// this.label = str; -// ctx = simulationArea.context; -// ctx.font = 14 + "px Georgia"; -// this.leftDimensionX = 10; -// this.rightDimensionX = ctx.measureText(this.label).width + 10; -// //console.log(this.leftDimensionX,this.rightDimensionX,ctx.measureText(this.label)) -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.label], -// } -// return data; -// } - -// keyDown(key) { - - -// if (key.length == 1) { -// if (this.label == "Enter Text Here") -// this.setLabel(key) -// else -// this.setLabel(this.label + key); -// } else if (key == "Backspace") { -// if (this.label == "Enter Text Here") -// this.setLabel("") -// else -// this.setLabel(this.label.slice(0, -1)); -// } -// } - -// draw() { - -// if (this.label.length == 0 && simulationArea.lastSelected != this) this.delete(); - -// ctx = simulationArea.context; -// ctx.strokeStyle = "black"; -// ctx.lineWidth = 1; - - - -// var xx = this.x; -// var yy = this.y; - -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) { -// ctx.beginPath(); -// ctx.fillStyle = "white"; -// rect2(ctx, -this.leftDimensionX, -this.upDimensionY, this.leftDimensionX + this.rightDimensionX, this.upDimensionY + this.downDimensionY, this.x, this.y, "RIGHT"); -// ctx.fillStyle = "rgba(255, 255, 32,0.1)"; -// ctx.fill(); -// ctx.stroke(); -// } -// ctx.beginPath(); -// ctx.textAlign = "left"; -// ctx.fillStyle = "black" -// fillText(ctx, this.label, xx, yy + 5, 14); -// ctx.fill(); - -// } -// } - -// Text.prototype.tooltipText = "Text ToolTip: Use this to document your circuit." - -// class TriState extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.setDimensions(15, 15); - -// this.inp1 = new Node(-10, 0, 0, this); -// this.output1 = new Node(20, 0, 1, this); -// this.state = new Node(0, 0, 0, this, 1, "Enable"); - - -// } - -// // TriState.prototype.propagationDelay=10000; -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth], -// nodes: { -// output1: findNode(this.output1), -// inp1: findNode(this.inp1), -// state: findNode(this.state), -// }, -// } -// return data; -// } - -// // TriState.prototype.isResolvable = function(){ -// // return this.inp1.value!=undefined -// // } -// newBitWidth(bitWidth) { -// this.inp1.bitWidth = bitWidth; -// this.output1.bitWidth = bitWidth; -// this.bitWidth = bitWidth; -// } - -// resolve() { -// if (this.isResolvable() == false) { -// return; -// } - -// if (this.state.value == 1) { -// if (this.output1.value != this.inp1.value) { -// this.output1.value = this.inp1.value; //>>>0)<<(32-this.bitWidth))>>>(32-this.bitWidth); -// simulationArea.simulationQueue.add(this.output1); -// } -// simulationArea.contentionPending.clean(this); -// } else { -// if (this.output1.value != undefined && !simulationArea.contentionPending.contains(this)) { -// this.output1.value = undefined; -// simulationArea.simulationQueue.add(this.output1); -// } - -// } -// simulationArea.contentionPending.clean(this); -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; -// ctx.beginPath(); -// ctx.fillStyle = "white"; -// moveTo(ctx, -10, -15, xx, yy, this.direction); -// lineTo(ctx, 20, 0, xx, yy, this.direction); -// lineTo(ctx, -10, 15, xx, yy, this.direction); -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - -// } -// } - -// TriState.prototype.tooltipText = "TriState ToolTip : Effectively removes the output from the circuit."; - -// class Buffer extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.setDimensions(15, 15); - -// this.state = 0; -// this.preState = 0; -// this.inp1 = new Node(-10, 0, 0, this); -// this.reset = new Node(0, 0, 0, this, 1, "reset"); -// this.output1 = new Node(20, 0, 1, this); - - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth], -// nodes: { -// output1: findNode(this.output1), -// inp1: findNode(this.inp1), -// reset: findNode(this.reset), -// }, -// } -// return data; -// } - -// newBitWidth(bitWidth) { -// this.inp1.bitWidth = bitWidth; -// this.output1.bitWidth = bitWidth; -// this.bitWidth = bitWidth; -// } - -// isResolvable() { -// return true; -// } - -// resolve() { - -// if (this.reset.value == 1) { -// this.state = this.preState; -// } -// if (this.inp1.value !== undefined) -// this.state = this.inp1.value; - -// this.output1.value = this.state; -// simulationArea.simulationQueue.add(this.output1); - -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.strokeStyle = ("rgba(200,0,0,1)"); -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; -// ctx.beginPath(); -// ctx.fillStyle = "white"; -// moveTo(ctx, -10, -15, xx, yy, this.direction); -// lineTo(ctx, 20, 0, xx, yy, this.direction); -// lineTo(ctx, -10, 15, xx, yy, this.direction); -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - -// } -// } - -// Buffer.prototype.tooltipText = "Buffer ToolTip : Isolate the input from the output."; - -// class ControlledInverter extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.setDimensions(15, 15); - -// this.inp1 = new Node(-10, 0, 0, this); -// this.output1 = new Node(30, 0, 1, this); -// this.state = new Node(0, 0, 0, this, 1, "Enable"); - - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth], -// nodes: { -// output1: findNode(this.output1), -// inp1: findNode(this.inp1), -// state: findNode(this.state) -// }, -// } -// return data; -// } - -// newBitWidth(bitWidth) { -// this.inp1.bitWidth = bitWidth; -// this.output1.bitWidth = bitWidth; -// this.bitWidth = bitWidth; -// } - -// resolve() { -// if (this.isResolvable() == false) { -// return; -// } -// if (this.state.value == 1) { -// this.output1.value = ((~this.inp1.value >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); -// simulationArea.simulationQueue.add(this.output1); -// } -// if (this.state.value == 0) { -// this.output1.value = undefined; -// } -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; -// ctx.beginPath(); -// ctx.fillStyle = "white"; -// moveTo(ctx, -10, -15, xx, yy, this.direction); -// lineTo(ctx, 20, 0, xx, yy, this.direction); -// lineTo(ctx, -10, 15, xx, yy, this.direction); -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); -// ctx.beginPath(); -// drawCircle2(ctx, 25, 0, 5, xx, yy, this.direction); -// ctx.stroke(); - -// } -// } - -// ControlledInverter.prototype.tooltipText = "Controlled Inverter ToolTip : Controlled buffer and NOT gate."; - -// class Adder extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { - -// super(x, y, scope, dir, bitWidth); -// this.setDimensions(20, 20); - -// this.inpA = new Node(-20, -10, 0, this, this.bitWidth, "A"); -// this.inpB = new Node(-20, 0, 0, this, this.bitWidth, "B"); -// this.carryIn = new Node(-20, 10, 0, this, 1, "Cin"); -// this.sum = new Node(20, 0, 1, this, this.bitWidth, "Sum"); -// this.carryOut = new Node(20, 10, 1, this, 1, "Cout"); - - - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth], -// nodes: { -// inpA: findNode(this.inpA), -// inpB: findNode(this.inpB), -// carryIn: findNode(this.carryIn), -// carryOut: findNode(this.carryOut), -// sum: findNode(this.sum) -// }, -// } -// return data; -// } - -// isResolvable() { -// return this.inpA.value != undefined && this.inpB.value != undefined; -// } - -// newBitWidth(bitWidth) { -// this.bitWidth = bitWidth; -// this.inpA.bitWidth = bitWidth; -// this.inpB.bitWidth = bitWidth; -// this.sum.bitWidth = bitWidth; -// } - -// resolve() { -// if (this.isResolvable() == false) { -// return; -// } -// var carryIn = this.carryIn.value; -// if (carryIn == undefined) carryIn = 0; -// var sum = this.inpA.value + this.inpB.value + carryIn; - -// this.sum.value = ((sum) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); -// this.carryOut.value = +((sum >>> (this.bitWidth)) !== 0); -// simulationArea.simulationQueue.add(this.carryOut); -// simulationArea.simulationQueue.add(this.sum); -// } -// } - -// Adder.prototype.tooltipText = "Adder ToolTip : Performs addition of numbers."; - -// class Rom extends CircuitElement { -// constructor( -// x, -// y, -// scope = globalScope, -// data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -// ) { - -// super(x, y, scope, "RIGHT", 1); -// this.fixedBitWidth = true; -// this.directionFixed = true; -// this.rectangleObject = false; -// this.setDimensions(80, 50); - -// this.memAddr = new Node(-80, 0, 0, this, 4, "Address"); -// this.en = new Node(0, 50, 0, this, 1, "Enable"); -// this.dataOut = new Node(80, 0, 1, this, 8, "DataOut"); -// this.data = data || prompt("Enter data").split(' ').map(function (x) { -// return parseInt(x, 16); -// }); -// //console.log(this.data); - - - -// } - -// isResolvable() { -// if ((this.en.value == 1 || this.en.connections.length == 0) && this.memAddr.value != undefined) return true; -// return false; -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.data], -// nodes: { -// memAddr: findNode(this.memAddr), -// dataOut: findNode(this.dataOut), -// en: findNode(this.en), -// }, - -// } -// return data; -// } - -// findPos() { -// var i = Math.floor((simulationArea.mouseX - this.x + 35) / 20) -// var j = Math.floor((simulationArea.mouseY - this.y + 35) / 16); -// if (i < 0 || j < 0 || i > 3 || j > 3) return undefined; -// return j * 4 + i; -// } - -// click() { // toggle -// this.selectedIndex = this.findPos(); -// } - -// keyDown(key) { -// if (key == "Backspace") this.delete(); -// if (this.selectedIndex == undefined) return; -// key = key.toLowerCase(); -// if (!~"1234567890abcdef".indexOf(key)) return; -// else { -// this.data[this.selectedIndex] = (this.data[this.selectedIndex] * 16 + parseInt(key, 16)) % 256; -// } -// } - -// customDraw() { - - - - -// var ctx = simulationArea.context; -// var xx = this.x; -// var yy = this.y; - -// var hoverIndex = this.findPos(); - - - - -// ctx.strokeStyle = "black"; -// ctx.fillStyle = "white"; -// ctx.lineWidth = correctWidth(3); -// ctx.beginPath(); -// rect2(ctx, -this.leftDimensionX, -this.upDimensionY, this.leftDimensionX + this.rightDimensionX, this.upDimensionY + this.downDimensionY, this.x, this.y, [this.direction, "RIGHT"][+this.directionFixed]); -// if (hoverIndex == undefined && ((!simulationArea.shiftDown && this.hover) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this))) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); -// // if (this.hover) -// // ////console.log(this); - -// ctx.strokeStyle = "black"; -// ctx.fillStyle = "#fafafa"; -// ctx.lineWidth = correctWidth(1); -// ctx.beginPath(); - -// for (var i = 0; i < 16; i += 4) { -// for (var j = i; j < i + 4; j++) { -// rect2(ctx, (j % 4) * 20, i * 4, 20, 16, xx - 35, yy - 35); -// } -// } -// ctx.fill(); -// ctx.stroke(); - -// if (hoverIndex != undefined) { -// ctx.beginPath(); -// ctx.fillStyle = "yellow"; -// rect2(ctx, (hoverIndex % 4) * 20, Math.floor(hoverIndex / 4) * 16, 20, 16, xx - 35, yy - 35); -// ctx.fill(); -// ctx.stroke(); -// } -// if (this.selectedIndex != undefined) { -// ctx.beginPath(); -// ctx.fillStyle = "lightgreen"; -// rect2(ctx, (this.selectedIndex % 4) * 20, Math.floor(this.selectedIndex / 4) * 16, 20, 16, xx - 35, yy - 35); -// ctx.fill(); -// ctx.stroke(); -// } -// if (this.memAddr.value != undefined) { -// ctx.beginPath(); -// ctx.fillStyle = "green"; -// rect2(ctx, (this.memAddr.value % 4) * 20, Math.floor(this.memAddr.value / 4) * 16, 20, 16, xx - 35, yy - 35); -// ctx.fill(); -// ctx.stroke(); -// } - -// ctx.beginPath(); -// ctx.fillStyle = "Black"; -// fillText3(ctx, "A", -65, 5, xx, yy, fontSize = 16, font = "Georgia", textAlign = "right"); -// fillText3(ctx, "D", 75, 5, xx, yy, fontSize = 16, font = "Georgia", textAlign = "right"); -// fillText3(ctx, "En", 5, 47, xx, yy, fontSize = 16, font = "Georgia", textAlign = "right"); -// ctx.fill(); - - -// ctx.beginPath(); -// ctx.fillStyle = "Black"; -// for (var i = 0; i < 16; i += 4) { -// for (var j = i; j < i + 4; j++) { -// var s = this.data[j].toString(16); -// if (s.length < 2) s = '0' + s; -// fillText3(ctx, s, (j % 4) * 20, i * 4, xx - 35 + 10, yy - 35 + 12, fontSize = 14, font = "Georgia", textAlign = "center") -// } -// } -// ctx.fill(); - -// ctx.beginPath(); -// ctx.fillStyle = "Black"; -// for (var i = 0; i < 16; i += 4) { - -// var s = i.toString(16); -// if (s.length < 2) s = '0' + s; -// fillText3(ctx, s, 0, i * 4, xx - 40, yy - 35 + 12, fontSize = 14, font = "Georgia", textAlign = "right") - -// } -// ctx.fill(); - - -// } - -// resolve() { -// if (this.isResolvable() == false) { -// return; -// } -// this.dataOut.value = this.data[this.memAddr.value]; -// simulationArea.simulationQueue.add(this.dataOut); -// } -// } - -// Rom.prototype.tooltipText = "Read-only memory"; - -// class Splitter extends CircuitElement { -// constructor( -// x, -// y, -// scope = globalScope, -// dir = "RIGHT", -// bitWidth = undefined, -// bitWidthSplit = undefined -// ) { - -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; - -// this.bitWidthSplit = bitWidthSplit || prompt("Enter bitWidth Split").split(' ').filter(x => x != '').map(function (x) { -// return parseInt(x, 10) || 1; - -// }); -// this.splitCount = this.bitWidthSplit.length; - -// this.setDimensions(10, (this.splitCount - 1) * 10 + 10); -// this.yOffset = (this.splitCount / 2 - 1) * 20; - -// this.inp1 = new Node(-10, 10 + this.yOffset, 0, this, this.bitWidth); - -// this.outputs = []; -// // this.prevOutValues=new Array(this.splitCount) -// for (var i = 0; i < this.splitCount; i++) -// this.outputs.push(new Node(20, i * 20 - this.yOffset - 20, 0, this, this.bitWidthSplit[i])); - -// this.prevInpValue = undefined; - - -// } - -// customSave() { -// var data = { - -// constructorParamaters: [this.direction, this.bitWidth, this.bitWidthSplit], -// nodes: { -// outputs: this.outputs.map(findNode), -// inp1: findNode(this.inp1) -// }, -// } -// return data; -// } - -// removePropagation() { - -// if (this.inp1.value == undefined) { -// let i = 0; - -// for (i = 0; i < this.outputs.length; i++) { // False Hit -// if (this.outputs[i].value == undefined) return; -// } - -// for (i = 0; i < this.outputs.length; i++) { -// if (this.outputs[i].value !== undefined) { -// this.outputs[i].value = undefined; -// simulationArea.simulationQueue.add(this.outputs[i]); -// } -// } -// } else { -// if (this.inp1.value !== undefined) { -// this.inp1.value = undefined; -// simulationArea.simulationQueue.add(this.inp1); -// } -// } - -// this.prevInpValue = undefined; - -// } - -// isResolvable() { -// var resolvable = false; -// if (this.inp1.value != this.prevInpValue) { -// if (this.inp1.value !== undefined) return true; -// return false; -// } -// var i; -// for (i = 0; i < this.splitCount; i++) -// if (this.outputs[i].value === undefined) break; -// if (i == this.splitCount) resolvable = true; -// return resolvable; -// } - -// resolve() { -// if (this.isResolvable() == false) { -// return; -// } -// if (this.inp1.value !== undefined && this.inp1.value != this.prevInpValue) { -// var bitCount = 1; -// for (var i = 0; i < this.splitCount; i++) { -// var bitSplitValue = extractBits(this.inp1.value, bitCount, bitCount + this.bitWidthSplit[i] - 1); -// if (this.outputs[i].value != bitSplitValue) { -// if (this.outputs[i].value != bitSplitValue) { -// this.outputs[i].value = bitSplitValue; -// simulationArea.simulationQueue.add(this.outputs[i]); -// } -// } -// bitCount += this.bitWidthSplit[i]; -// } -// } else { -// var n = 0; -// for (var i = this.splitCount - 1; i >= 0; i--) { -// n <<= this.bitWidthSplit[i]; -// n += this.outputs[i].value; -// } -// if (this.inp1.value != n) { -// this.inp1.value = n; -// simulationArea.simulationQueue.add(this.inp1); -// } -// // else if (this.inp1.value != n) { -// // console.log("CONTENTION"); -// // } -// } -// this.prevInpValue = this.inp1.value; -// } - -// reset() { -// this.prevInpValue = undefined; -// } - -// processVerilog() { - -// // console.log(this.inp1.verilogLabel +":"+ this.outputs[0].verilogLabel); -// if (this.inp1.verilogLabel != "" && this.outputs[0].verilogLabel == "") { -// var bitCount = 0; -// for (var i = 0; i < this.splitCount; i++) { -// // var bitSplitValue = extractBits(this.inp1.value, bitCount, bitCount + this.bitWidthSplit[i] - 1); -// if (this.bitWidthSplit[i] > 1) -// var label = this.inp1.verilogLabel + '[' + (bitCount + this.bitWidthSplit[i] - 1) + ":" + bitCount + "]"; -// else -// var label = this.inp1.verilogLabel + '[' + bitCount + "]"; -// if (this.outputs[i].verilogLabel != label) { -// this.outputs[i].verilogLabel = label; -// this.scope.stack.push(this.outputs[i]); -// } -// bitCount += this.bitWidthSplit[i]; -// } -// } else if (this.inp1.verilogLabel == "" && this.outputs[0].verilogLabel != "") { - - -// var label = "{" + this.outputs.map((x) => { -// return x.verilogLabel -// }).join(",") + "}"; -// // console.log("HIT",label) -// if (this.inp1.verilogLabel != label) { -// this.inp1.verilogLabel = label; -// this.scope.stack.push(this.inp1); -// } -// } -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.strokeStyle = ["black", "brown"][((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) + 0]; -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; -// ctx.beginPath(); - -// // drawLine(ctx, -10, -10, xx, y2, color, width) -// moveTo(ctx, -10, 10 + this.yOffset, xx, yy, this.direction); -// lineTo(ctx, 0, 0 + this.yOffset, xx, yy, this.direction); -// lineTo(ctx, 0, -20 * (this.splitCount - 1) + this.yOffset, xx, yy, this.direction); - -// var bitCount = 0; -// for (var i = this.splitCount - 1; i >= 0; i--) { -// moveTo(ctx, 0, -20 * i + this.yOffset, xx, yy, this.direction); -// lineTo(ctx, 20, -20 * i + this.yOffset, xx, yy, this.direction); -// } -// ctx.stroke(); -// ctx.beginPath(); -// ctx.fillStyle = "black"; -// for (var i = this.splitCount - 1; i >= 0; i--) { -// fillText2(ctx, bitCount + ":" + (bitCount + this.bitWidthSplit[this.splitCount - i - 1]), 12, -20 * i + this.yOffset + 10, xx, yy, this.direction); -// bitCount += this.bitWidthSplit[this.splitCount - i - 1]; -// } -// ctx.fill(); - - - -// } -// } - -// Splitter.prototype.tooltipText = "Splitter ToolTip: Split multiBit Input into smaller bitwidths or vice versa." - -// class Ground extends CircuitElement { -// constructor(x, y, scope = globalScope, bitWidth = 1) { -// super(x, y, scope, "RIGHT", bitWidth); -// this.rectangleObject = false; -// this.setDimensions(10, 10); -// this.directionFixed = true; -// this.output1 = new Node(0, -10, 1, this); -// } - -// customSave() { -// var data = { -// nodes: { -// output1: findNode(this.output1) -// }, -// values: { -// state: this.state -// }, -// constructorParamaters: [this.direction, this.bitWidth] -// } -// return data; -// } - -// resolve() { -// this.output1.value = 0; -// simulationArea.simulationQueue.add(this.output1); -// } - -// customSave() { -// var data = { -// nodes: { -// output1: findNode(this.output1) -// }, -// constructorParamaters: [this.bitWidth], -// } -// return data; -// } - -// customDraw() { - -// ctx = simulationArea.context; - -// ctx.beginPath(); -// ctx.strokeStyle = ["black", "brown"][((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) + 0]; -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; - -// moveTo(ctx, 0, -10, xx, yy, this.direction); -// lineTo(ctx, 0, 0, xx, yy, this.direction); -// moveTo(ctx, -10, 0, xx, yy, this.direction); -// lineTo(ctx, 10, 0, xx, yy, this.direction); -// moveTo(ctx, -6, 5, xx, yy, this.direction); -// lineTo(ctx, 6, 5, xx, yy, this.direction); -// moveTo(ctx, -2.5, 10, xx, yy, this.direction); -// lineTo(ctx, 2.5, 10, xx, yy, this.direction); -// ctx.stroke(); -// } -// } - -// Ground.prototype.tooltipText = "Ground: All bits are Low(0)."; -// Ground.prototype.propagationDelay = 0; - -// class Power extends CircuitElement { -// constructor(x, y, scope = globalScope, bitWidth = 1) { - -// super(x, y, scope, "RIGHT", bitWidth); -// this.directionFixed = true; -// this.rectangleObject = false; -// this.setDimensions(10, 10); -// this.output1 = new Node(0, 10, 1, this); -// } - -// resolve() { -// this.output1.value = ~0 >>> (32 - this.bitWidth); -// simulationArea.simulationQueue.add(this.output1); -// } - -// customSave() { -// var data = { - -// nodes: { -// output1: findNode(this.output1) -// }, -// constructorParamaters: [this.bitWidth], -// } -// return data; -// } - -// customDraw() { - -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// ctx.beginPath(); -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(3); -// ctx.fillStyle = "green"; -// moveTo(ctx, 0, -10, xx, yy, this.direction); -// lineTo(ctx, -10, 0, xx, yy, this.direction); -// lineTo(ctx, 10, 0, xx, yy, this.direction); -// lineTo(ctx, 0, -10, xx, yy, this.direction); -// ctx.closePath(); -// ctx.stroke(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// moveTo(ctx, 0, 0, xx, yy, this.direction); -// lineTo(ctx, 0, 10, xx, yy, this.direction); -// ctx.stroke(); - -// } -// } - -// Power.prototype.tooltipText = "Power: All bits are High(1)."; -// Power.prototype.propagationDelay = 0; - -// function get_next_position(x = 0, scope = globalScope) { -// var possible_y = 20; - -// var done = {} -// for (var i = 0; i < scope.Input.length; i++) -// if (scope.Input[i].layoutProperties.x == x) -// done[scope.Input[i].layoutProperties.y] = 1 -// for (var i = 0; i < scope.Output.length; i++) -// if (scope.Output[i].layoutProperties.x == x) -// done[scope.Output[i].layoutProperties.y] = 1 - -// // console.log(done) -// // return possible_y; - -// while (done[possible_y] || done[possible_y + 10] || done[possible_y - 10]) -// possible_y += 10; - - -// var height = possible_y + 20; -// if (height > scope.layout.height) { -// var old_height = scope.layout.height -// scope.layout.height = height; -// for (var i = 0; i < scope.Input.length; i++) { -// if (scope.Input[i].layoutProperties.y == old_height) -// scope.Input[i].layoutProperties.y = scope.layout.height; -// } -// for (var i = 0; i < scope.Output.length; i++) { -// if (scope.Output[i].layoutProperties.y == old_height) -// scope.Output[i].layoutProperties.y = scope.layout.height; -// } -// } -// return possible_y; -// } - -// class Input extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1, layoutProperties) { - -// if (layoutProperties) -// this.layoutProperties = layoutProperties; -// else { -// this.layoutProperties = { -// x: 0, -// y: get_next_position(0, scope), -// id: generateId(), -// } -// } - -// // Call base class constructor -// super(x, y, scope, dir, bitWidth); -// this.state = 0; -// this.orientationFixed = false; -// this.state = bin2dec(this.state); // in integer format -// this.output1 = new Node(this.bitWidth * 10, 0, 1, this); -// this.wasClicked = false; -// this.directionFixed = true; -// this.setWidth(this.bitWidth * 10); -// this.rectangleObject = true; // Trying to make use of base class draw - - - -// } - -// customSave() { -// var data = { -// nodes: { -// output1: findNode(this.output1) -// }, -// values: { -// state: this.state -// }, -// constructorParamaters: [this.direction, this.bitWidth, this.layoutProperties] -// } -// return data; -// } - -// resolve() { -// this.output1.value = this.state; -// simulationArea.simulationQueue.add(this.output1); -// } - -// // Check if override is necessary!! -// newBitWidth(bitWidth) { -// if (bitWidth < 1) return; -// var diffBitWidth = bitWidth - this.bitWidth; -// this.bitWidth = bitWidth; //||parseInt(prompt("Enter bitWidth"),10); -// this.setWidth(this.bitWidth * 10); -// this.state = 0; -// this.output1.bitWidth = bitWidth; -// if (this.direction == "RIGHT") { -// this.x -= 10 * diffBitWidth; -// this.output1.x = 10 * this.bitWidth; -// this.output1.leftx = 10 * this.bitWidth; -// } else if (this.direction == "LEFT") { -// this.x += 10 * diffBitWidth; -// this.output1.x = -10 * this.bitWidth; -// this.output1.leftx = 10 * this.bitWidth; -// } -// } - -// click() { // toggle -// var pos = this.findPos(); -// if (pos == 0) pos = 1; // minor correction -// if (pos < 1 || pos > this.bitWidth) return; -// this.state = ((this.state >>> 0) ^ (1 << (this.bitWidth - pos))) >>> 0; -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(3); -// var xx = this.x; -// var yy = this.y; - -// ctx.beginPath(); -// ctx.fillStyle = "green"; -// ctx.textAlign = "center"; -// var bin = dec2bin(this.state, this.bitWidth); -// for (var k = 0; k < this.bitWidth; k++) -// fillText(ctx, bin[k], xx - 10 * this.bitWidth + 10 + (k) * 20, yy + 5); -// ctx.fill(); - - -// } - -// newDirection(dir) { -// if (dir == this.direction) return; -// this.direction = dir; -// this.output1.refresh(); -// if (dir == "RIGHT" || dir == "LEFT") { -// this.output1.leftx = 10 * this.bitWidth; -// this.output1.lefty = 0; -// } else { -// this.output1.leftx = 10; //10*this.bitWidth; -// this.output1.lefty = 0; -// } - -// this.output1.refresh(); -// this.labelDirection = oppositeDirection[this.direction]; -// } - -// findPos() { -// return Math.round((simulationArea.mouseX - this.x + 10 * this.bitWidth) / 20.0); -// } -// } - -// Input.prototype.tooltipText = "Input ToolTip: Toggle the individual bits by clicking on them." -// Input.prototype.propagationDelay = 0; - -// class Output extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "LEFT", bitWidth = 1, layoutProperties) { -// // Calling base class constructor - -// if (layoutProperties) -// this.layoutProperties = layoutProperties -// else { -// this.layoutProperties = { -// x: scope.layout.width, -// y: get_next_position(scope.layout.width, scope), -// id: generateId(), -// } -// } - - -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.directionFixed = true; -// this.orientationFixed = false; -// this.setDimensions(this.bitWidth * 10, 10); -// this.inp1 = new Node(this.bitWidth * 10, 0, 0, this); - - -// } - -// generateVerilog() { -// return "assign " + this.label + " = " + this.inp1.verilogLabel + ";" -// } - -// customSave() { -// var data = { -// nodes: { -// inp1: findNode(this.inp1) -// }, -// constructorParamaters: [this.direction, this.bitWidth, this.layoutProperties], -// } -// return data; -// } - -// newBitWidth(bitWidth) { -// if (bitWidth < 1) return; -// var diffBitWidth = bitWidth - this.bitWidth; -// this.state = undefined; -// this.inp1.bitWidth = bitWidth; -// this.bitWidth = bitWidth; -// this.setWidth(10 * this.bitWidth); - -// if (this.direction == "RIGHT") { -// this.x -= 10 * diffBitWidth; -// this.inp1.x = 10 * this.bitWidth; -// this.inp1.leftx = 10 * this.bitWidth; -// } else if (this.direction == "LEFT") { -// this.x += 10 * diffBitWidth; -// this.inp1.x = -10 * this.bitWidth; -// this.inp1.leftx = 10 * this.bitWidth; -// } -// } - -// customDraw() { -// this.state = this.inp1.value; -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = ["blue", "red"][+(this.inp1.value == undefined)]; -// ctx.fillStyle = "white"; -// ctx.lineWidth = correctWidth(3); -// var xx = this.x; -// var yy = this.y; - -// rect2(ctx, -10 * this.bitWidth, -10, 20 * this.bitWidth, 20, xx, yy, "RIGHT"); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) -// ctx.fillStyle = "rgba(255, 255, 32,0.8)"; - -// ctx.fill(); -// ctx.stroke(); - -// ctx.beginPath(); -// ctx.font = "20px Georgia"; -// ctx.fillStyle = "green"; -// ctx.textAlign = "center"; -// if (this.state === undefined) -// var bin = 'x'.repeat(this.bitWidth); -// else -// var bin = dec2bin(this.state, this.bitWidth); - -// for (var k = 0; k < this.bitWidth; k++) -// fillText(ctx, bin[k], xx - 10 * this.bitWidth + 10 + (k) * 20, yy + 5); -// ctx.fill(); - -// } - -// newDirection(dir) { -// if (dir == this.direction) return; -// this.direction = dir; -// this.inp1.refresh(); -// if (dir == "RIGHT" || dir == "LEFT") { -// this.inp1.leftx = 10 * this.bitWidth; -// this.inp1.lefty = 0; -// } else { -// this.inp1.leftx = 10; //10*this.bitWidth; -// this.inp1.lefty = 0; -// } - -// this.inp1.refresh(); -// this.labelDirection = oppositeDirection[this.direction]; -// } -// } - -// Output.prototype.tooltipText = "Output ToolTip: Simple output element showing output in binary." -// Output.prototype.propagationDelay = 0; - -// class BitSelector extends CircuitElement { -// constructor( -// x, -// y, -// scope = globalScope, -// dir = "RIGHT", -// bitWidth = 2, -// selectorBitWidth = 1 -// ) { - -// super(x, y, scope, dir, bitWidth); -// this.setDimensions(20, 20); -// this.selectorBitWidth = selectorBitWidth || parseInt(prompt("Enter Selector bitWidth"), 10); -// this.rectangleObject = false; -// this.inp1 = new Node(-20, 0, 0, this, this.bitWidth, "Input"); -// this.output1 = new Node(20, 0, 1, this, 1, "Output"); -// this.bitSelectorInp = new Node(0, 20, 0, this, this.selectorBitWidth, "Bit Selector"); - -// } - -// changeSelectorBitWidth(size) { -// if (size == undefined || size < 1 || size > 32) return; -// this.selectorBitWidth = size; -// this.bitSelectorInp.bitWidth = size; -// } - -// customSave() { -// var data = { - -// nodes: { -// inp1: findNode(this.inp1), -// output1: findNode(this.output1), -// bitSelectorInp: findNode(this.bitSelectorInp) -// }, -// constructorParamaters: [this.direction, this.bitWidth, this.selectorBitWidth], -// } -// return data; -// } - -// newBitWidth(bitWidth) { -// this.inp1.bitWidth = bitWidth; -// this.bitWidth = bitWidth; -// } - -// resolve() { -// this.output1.value = extractBits(this.inp1.value, this.bitSelectorInp.value + 1, this.bitSelectorInp.value + 1); //(this.inp1.value^(1< this.state.length) this.state = '0'.repeat(bitWidth - this.state.length) + this.state; -// else if (bitWidth < this.state.length) this.state = this.state.slice(this.bitWidth - bitWidth); -// this.bitWidth = bitWidth; //||parseInt(prompt("Enter bitWidth"),10); -// this.output1.bitWidth = bitWidth; -// this.setDimensions(10 * this.bitWidth, 10); -// if (this.direction == "RIGHT") { -// this.output1.x = 10 * this.bitWidth; -// this.output1.leftx = 10 * this.bitWidth; -// } else if (this.direction == "LEFT") { -// this.output1.x = -10 * this.bitWidth; -// this.output1.leftx = 10 * this.bitWidth; -// } -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.fillStyle = "white"; -// ctx.lineWidth = correctWidth(1); -// var xx = this.x; -// var yy = this.y; - -// rect2(ctx, -10 * this.bitWidth, -10, 20 * this.bitWidth, 20, xx, yy, "RIGHT"); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - -// ctx.beginPath(); -// ctx.fillStyle = "green"; -// ctx.textAlign = "center"; -// var bin = this.state; //dec2bin(this.state,this.bitWidth); -// for (var k = 0; k < this.bitWidth; k++) -// fillText(ctx, bin[k], xx - 10 * this.bitWidth + 10 + (k) * 20, yy + 5); -// ctx.fill(); - -// } - -// newDirection(dir) { -// if (dir == this.direction) return; -// this.direction = dir; -// this.output1.refresh(); -// if (dir == "RIGHT" || dir == "LEFT") { -// this.output1.leftx = 10 * this.bitWidth; -// this.output1.lefty = 0; -// } else { -// this.output1.leftx = 10; //10*this.bitWidth; -// this.output1.lefty = 0; -// } - -// this.output1.refresh(); -// this.labelDirection = oppositeDirection[this.direction]; -// } -// } - -// ConstantVal.prototype.tooltipText = "Constant ToolTip: Bits are fixed. Double click element to change the bits." -// ConstantVal.prototype.propagationDelay = 0; - -// class NorGate extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", inputs = 2, bitWidth = 1) { - -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.setDimensions(15, 20); - -// this.inp = []; -// this.inputSize = inputs; - -// if (inputs % 2 == 1) { -// for (var i = 0; i < inputs / 2 - 1; i++) { -// var a = new Node(-10, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// var a = new Node(-10, 0, 0, this); -// this.inp.push(a); -// for (var i = inputs / 2 + 1; i < inputs; i++) { -// var a = new Node(-10, 10 * (i + 1 - inputs / 2 - 1), 0, this); -// this.inp.push(a); -// } -// } else { -// for (var i = 0; i < inputs / 2; i++) { -// var a = new Node(-10, -10 * (i + 1), 0, this); -// this.inp.push(a); -// } -// for (var i = inputs / 2; i < inputs; i++) { -// var a = new Node(-10, 10 * (i + 1 - inputs / 2), 0, this); -// this.inp.push(a); -// } -// } -// this.output1 = new Node(30, 0, 1, this); - - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.inputSize, this.bitWidth], -// nodes: { -// inp: this.inp.map(findNode), -// output1: findNode(this.output1) -// }, -// } -// return data; -// } - -// resolve() { -// var result = this.inp[0].value || 0; -// for (var i = 1; i < this.inputSize; i++) -// result = result | (this.inp[i].value || 0); -// result = ((~result >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); -// this.output1.value = result -// simulationArea.simulationQueue.add(this.output1); -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(3); - -// var xx = this.x; -// var yy = this.y; -// ctx.beginPath(); -// ctx.fillStyle = "white"; - -// moveTo(ctx, -10, -20, xx, yy, this.direction, true); -// bezierCurveTo(0, -20, +15, -10, 20, 0, xx, yy, this.direction); -// bezierCurveTo(0 + 15, 0 + 10, 0, 0 + 20, -10, +20, xx, yy, this.direction); -// bezierCurveTo(0, 0, 0, 0, -10, -20, xx, yy, this.direction); -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.5)"; -// ctx.fill(); -// ctx.stroke(); -// ctx.beginPath(); -// drawCircle2(ctx, 25, 0, 5, xx, yy, this.direction); -// ctx.stroke(); -// //for debugging -// } -// } - -// NorGate.prototype.tooltipText = "Nor Gate ToolTip : Combination of OR gate and NOT gate."; -// NorGate.prototype.alwaysResolve = true; -// NorGate.prototype.changeInputSize = changeInputSize; -// NorGate.prototype.verilogType = "nor"; - -// class DigitalLed extends CircuitElement { -// constructor(x, y, scope = globalScope, color = "Red") { -// // Calling base class constructor - -// super(x, y, scope, "UP", 1); -// this.rectangleObject = false; -// this.setDimensions(10, 20); -// this.inp1 = new Node(-40, 0, 0, this, 1); -// this.directionFixed = true; -// this.fixedBitWidth = true; -// this.color = color; -// var temp = colorToRGBA(this.color) -// this.actualColor = "rgba(" + temp[0] + "," + temp[1] + "," + temp[2] + "," + 0.8 + ")"; - - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.color], -// nodes: { -// inp1: findNode(this.inp1) -// }, -// } -// return data; -// } - -// changeColor(value) { -// if (validColor(value)) { -// this.color = value; -// var temp = colorToRGBA(this.color) -// this.actualColor = "rgba(" + temp[0] + "," + temp[1] + "," + temp[2] + "," + 0.8 + ")"; -// } - -// } - -// customDraw() { - -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// ctx.strokeStyle = "#e3e4e5"; -// ctx.lineWidth = correctWidth(3); -// ctx.beginPath(); -// moveTo(ctx, -20, 0, xx, yy, this.direction); -// lineTo(ctx, -40, 0, xx, yy, this.direction); -// ctx.stroke(); - -// ctx.strokeStyle = "#d3d4d5"; -// ctx.fillStyle = ["rgba(227,228,229,0.8)", this.actualColor][this.inp1.value || 0]; -// ctx.lineWidth = correctWidth(1); - -// ctx.beginPath(); - -// moveTo(ctx, -15, -9, xx, yy, this.direction); -// lineTo(ctx, 0, -9, xx, yy, this.direction); -// arc(ctx, 0, 0, 9, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); -// lineTo(ctx, -15, 9, xx, yy, this.direction); -// lineTo(ctx, -18, 12, xx, yy, this.direction); -// arc(ctx, 0, 0, Math.sqrt(468), ((Math.PI / 2) + Math.acos(12 / Math.sqrt(468))), ((-Math.PI / 2) - Math.asin(18 / Math.sqrt(468))), xx, yy, this.direction); -// lineTo(ctx, -15, -9, xx, yy, this.direction); -// ctx.stroke(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); - -// } -// } - -// DigitalLed.prototype.tooltipText = "Digital Led ToolTip: Digital LED glows high when input is High(1)." -// DigitalLed.prototype.mutableProperties = { -// "color": { -// name: "Color: ", -// type: "text", -// func: "changeColor", -// }, -// } - -// class VariableLed extends CircuitElement { -// constructor(x, y, scope = globalScope) { -// // Calling base class constructor - -// super(x, y, scope, "UP", 8); -// this.rectangleObject = false; -// this.setDimensions(10, 20); -// this.inp1 = new Node(-40, 0, 0, this, 8); -// this.directionFixed = true; -// this.fixedBitWidth = true; - - -// } - -// customSave() { -// var data = { -// nodes: { -// inp1: findNode(this.inp1) -// }, -// } -// return data; -// } - -// customDraw() { - -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// ctx.strokeStyle = "#353535"; -// ctx.lineWidth = correctWidth(3); -// ctx.beginPath(); -// moveTo(ctx, -20, 0, xx, yy, this.direction); -// lineTo(ctx, -40, 0, xx, yy, this.direction); -// ctx.stroke(); -// var c = this.inp1.value; -// var alpha = c / 255; -// ctx.strokeStyle = "#090a0a"; -// ctx.fillStyle = ["rgba(255,29,43," + alpha + ")", "rgba(227, 228, 229, 0.8)"][(c === undefined || c == 0) + 0]; -// ctx.lineWidth = correctWidth(1); - -// ctx.beginPath(); - -// moveTo(ctx, -20, -9, xx, yy, this.direction); -// lineTo(ctx, 0, -9, xx, yy, this.direction); -// arc(ctx, 0, 0, 9, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); -// lineTo(ctx, -20, 9, xx, yy, this.direction); -// /*lineTo(ctx,-18,12,xx,yy,this.direction); -// arc(ctx,0,0,Math.sqrt(468),((Math.PI/2) + Math.acos(12/Math.sqrt(468))),((-Math.PI/2) - Math.asin(18/Math.sqrt(468))),xx,yy,this.direction); - -// */ -// lineTo(ctx, -20, -9, xx, yy, this.direction); -// ctx.stroke(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); - -// } -// } - -// VariableLed.prototype.tooltipText = "Variable Led ToolTip: Variable LED inputs an 8 bit value and glows with a proportional intensity." - -// class Button extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT") { -// super(x, y, scope, dir, 1); -// this.state = 0; -// this.output1 = new Node(30, 0, 1, this); -// this.wasClicked = false; -// this.rectangleObject = false; -// this.setDimensions(10, 10); - - -// } - -// customSave() { -// var data = { -// nodes: { -// output1: findNode(this.output1) -// }, -// values: { -// state: this.state -// }, -// constructorParamaters: [this.direction, this.bitWidth] -// } -// return data; -// } - -// resolve() { -// if (this.wasClicked) { -// this.state = 1; -// this.output1.value = this.state; -// } else { -// this.state = 0; -// this.output1.value = this.state; -// } -// simulationArea.simulationQueue.add(this.output1); -// } - -// customDraw() { -// ctx = simulationArea.context; -// var xx = this.x; -// var yy = this.y; -// ctx.fillStyle = "#ddd"; - -// ctx.strokeStyle = "#353535"; -// ctx.lineWidth = correctWidth(5); - -// ctx.beginPath(); - -// moveTo(ctx, 10, 0, xx, yy, this.direction); -// lineTo(ctx, 30, 0, xx, yy, this.direction); -// ctx.stroke(); - -// ctx.beginPath(); - -// drawCircle2(ctx, 0, 0, 12, xx, yy, this.direction); -// ctx.stroke(); - -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) -// ctx.fillStyle = "rgba(232, 13, 13,0.6)" - -// if (this.wasClicked) -// ctx.fillStyle = "rgba(232, 13, 13,0.8)"; -// ctx.fill(); -// } -// } - -// Button.prototype.tooltipText = "Button ToolTip: High(1) when pressed and Low(0) when released." -// Button.prototype.propagationDelay = 0; - -// class RGBLed extends CircuitElement { -// constructor(x, y, scope = globalScope) { -// // Calling base class constructor - -// super(x, y, scope, "UP", 8); -// this.rectangleObject = false; -// this.inp = []; -// this.setDimensions(10, 10); -// this.inp1 = new Node(-40, -10, 0, this, 8); -// this.inp2 = new Node(-40, 0, 0, this, 8); -// this.inp3 = new Node(-40, 10, 0, this, 8); -// this.inp.push(this.inp1); -// this.inp.push(this.inp2); -// this.inp.push(this.inp3); -// this.directionFixed = true; -// this.fixedBitWidth = true; - - -// } - -// customSave() { -// var data = { -// nodes: { -// inp1: findNode(this.inp1), -// inp2: findNode(this.inp2), -// inp3: findNode(this.inp3), -// }, -// } -// return data; -// } - -// customDraw() { - -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// ctx.strokeStyle = "green"; -// ctx.lineWidth = correctWidth(3); -// ctx.beginPath(); -// moveTo(ctx, -20, 0, xx, yy, this.direction); -// lineTo(ctx, -40, 0, xx, yy, this.direction); -// ctx.stroke(); - -// ctx.strokeStyle = "red"; -// ctx.lineWidth = correctWidth(3); -// ctx.beginPath(); -// moveTo(ctx, -20, -10, xx, yy, this.direction); -// lineTo(ctx, -40, -10, xx, yy, this.direction); -// ctx.stroke(); - -// ctx.strokeStyle = "blue"; -// ctx.lineWidth = correctWidth(3); -// ctx.beginPath(); -// moveTo(ctx, -20, 10, xx, yy, this.direction); -// lineTo(ctx, -40, 10, xx, yy, this.direction); -// ctx.stroke(); - -// var a = this.inp1.value; -// var b = this.inp2.value; -// var c = this.inp3.value; -// ctx.strokeStyle = "#d3d4d5"; -// ctx.fillStyle = ["rgba(" + a + ", " + b + ", " + c + ", 0.8)", "rgba(227, 228, 229, 0.8)"][((a === undefined || b === undefined || c === undefined)) + 0] -// //ctx.fillStyle = ["rgba(200, 200, 200, 0.3)","rgba(227, 228, 229, 0.8)"][((a === undefined || b === undefined || c === undefined) || (a == 0 && b == 0 && c == 0)) + 0]; -// ctx.lineWidth = correctWidth(1); - -// ctx.beginPath(); - -// moveTo(ctx, -18, -11, xx, yy, this.direction); -// lineTo(ctx, 0, -11, xx, yy, this.direction); -// arc(ctx, 0, 0, 11, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); -// lineTo(ctx, -18, 11, xx, yy, this.direction); -// lineTo(ctx, -21, 15, xx, yy, this.direction); -// arc(ctx, 0, 0, Math.sqrt(666), ((Math.PI / 2) + Math.acos(15 / Math.sqrt(666))), ((-Math.PI / 2) - Math.asin(21 / Math.sqrt(666))), xx, yy, this.direction); -// lineTo(ctx, -18, -11, xx, yy, this.direction); -// ctx.stroke(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// } -// } - -// RGBLed.prototype.tooltipText = "RGB Led ToolTip: RGB Led inputs 8 bit values for the colors RED, GREEN and BLUE." - -// class SquareRGBLed extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "UP", pinLength = 1) { -// super(x, y, scope, dir, 8); -// this.rectangleObject = false; -// this.setDimensions(15, 15); -// this.pinLength = pinLength === undefined ? 1 : pinLength; -// var nodeX = -10 - 10 * pinLength; -// this.inp1 = new Node(nodeX, -10, 0, this, 8, "R"); -// this.inp2 = new Node(nodeX, 0, 0, this, 8, "G"); -// this.inp3 = new Node(nodeX, 10, 0, this, 8, "B"); -// this.inp = [this.inp1, this.inp2, this.inp3]; -// this.labelDirection = "UP"; -// this.fixedBitWidth = true; - -// this.changePinLength = function (pinLength) { -// if (pinLength == undefined) return; -// pinLength = parseInt(pinLength, 10); -// if (pinLength < 0 || pinLength > 1000) return; - -// // Calculate the new position of the LED, so the nodes will stay in the same place. -// var diff = 10 * (pinLength - this.pinLength); -// var diffX = this.direction == "LEFT" ? -diff : this.direction == "RIGHT" ? diff : 0; -// var diffY = this.direction == "UP" ? -diff : this.direction == "DOWN" ? diff : 0; - -// // Build a new LED with the new values; preserve label properties too. -// var obj = new window[this.objectType](this.x + diffX, this.y + diffY, this.scope, this.direction, pinLength); -// obj.label = this.label; -// obj.labelDirection = this.labelDirection; - -// this.cleanDelete(); -// simulationArea.lastSelected = obj; -// return obj; -// } - -// this.mutableProperties = { -// "pinLength": { -// name: "Pin Length", -// type: "number", -// max: "1000", -// min: "0", -// func: "changePinLength", -// }, -// } -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.pinLength], -// nodes: { -// inp1: findNode(this.inp1), -// inp2: findNode(this.inp2), -// inp3: findNode(this.inp3), -// }, -// } -// return data; -// } - -// customDraw() { -// var ctx = simulationArea.context; -// var xx = this.x; -// var yy = this.y; -// var r = this.inp1.value; -// var g = this.inp2.value; -// var b = this.inp3.value; - -// var colors = ["rgb(174,20,20)", "rgb(40,174,40)", "rgb(0,100,255)"]; -// for (var i = 0; i < 3; i++) { -// var x = -10 - 10 * this.pinLength; -// var y = i * 10 - 10; -// ctx.lineWidth = correctWidth(3); - -// // A gray line, which makes it easy on the eyes when the pin length is large -// ctx.beginPath(); -// ctx.lineCap = "butt"; -// ctx.strokeStyle = "rgb(227, 228, 229)"; -// moveTo(ctx, -15, y, xx, yy, this.direction); -// lineTo(ctx, x + 10, y, xx, yy, this.direction); -// ctx.stroke(); - -// // A colored line, so people know which pin does what. -// ctx.lineCap = "round"; -// ctx.beginPath(); -// ctx.strokeStyle = colors[i]; -// moveTo(ctx, x + 10, y, xx, yy, this.direction); -// lineTo(ctx, x, y, xx, yy, this.direction); -// ctx.stroke(); -// } - -// ctx.strokeStyle = "#d3d4d5"; -// ctx.fillStyle = (r === undefined && g === undefined && b === undefined) ? "rgb(227, 228, 229)" : "rgb(" + (r || 0) + ", " + (g || 0) + ", " + (b || 0) + ")"; -// ctx.lineWidth = correctWidth(1); -// ctx.beginPath(); -// rect2(ctx, -15, -15, 30, 30, xx, yy, this.direction); -// ctx.stroke(); - -// if ((this.hover && !simulationArea.shiftDown) || -// simulationArea.lastSelected == this || -// simulationArea.multipleObjectSelections.contains(this)) { -// ctx.fillStyle = "rgba(255, 255, 32)"; -// } - -// ctx.fill(); -// } -// } - -// SquareRGBLed.prototype.tooltipText = "Square RGB Led ToolTip: RGB Led inputs 8 bit values for the colors RED, GREEN and BLUE." - -// class Demultiplexer extends CircuitElement { -// constructor( -// x, -// y, -// scope = globalScope, -// dir = "LEFT", -// bitWidth = 1, -// controlSignalSize = 1 -// ) { - -// super(x, y, scope, dir, bitWidth); -// this.controlSignalSize = controlSignalSize || parseInt(prompt("Enter control signal bitWidth"), 10); -// this.outputsize = 1 << this.controlSignalSize; -// this.xOff = 0; -// this.yOff = 1; -// if (this.controlSignalSize == 1) { -// this.xOff = 10; -// } -// if (this.controlSignalSize <= 3) { -// this.yOff = 2; -// } - -// this.changeControlSignalSize = function (size) { -// if (size == undefined || size < 1 || size > 32) return; -// if (this.controlSignalSize == size) return; -// var obj = new window[this.objectType](this.x, this.y, this.scope, this.direction, this.bitWidth, size); -// this.cleanDelete(); -// simulationArea.lastSelected = obj; -// return obj; -// } -// this.mutableProperties = { -// "controlSignalSize": { -// name: "Control Signal Size", -// type: "number", -// max: "32", -// min: "1", -// func: "changeControlSignalSize", -// }, -// } -// this.newBitWidth = function (bitWidth) { -// this.bitWidth = bitWidth; -// for (var i = 0; i < this.outputsize; i++) { -// this.output1[i].bitWidth = bitWidth -// } -// this.input.bitWidth = bitWidth; -// } - -// this.setDimensions(20 - this.xOff, this.yOff * 5 * (this.outputsize)); -// this.rectangleObject = false; -// this.input = new Node(20 - this.xOff, 0, 0, this); - -// this.output1 = []; -// for (var i = 0; i < this.outputsize; i++) { -// var a = new Node(-20 + this.xOff, +this.yOff * 10 * (i - this.outputsize / 2) + 10, 1, this); -// this.output1.push(a); -// } - -// this.controlSignalInput = new Node(0, this.yOff * 10 * (this.outputsize / 2 - 1) + this.xOff + 10, 0, this, this.controlSignalSize, "Control Signal"); - - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth, this.controlSignalSize], -// nodes: { -// output1: this.output1.map(findNode), -// input: findNode(this.input), -// controlSignalInput: findNode(this.controlSignalInput) -// }, -// } -// return data; -// } - -// resolve() { - -// for (var i = 0; i < this.output1.length; i++) -// this.output1[i].value = 0; - -// this.output1[this.controlSignalInput.value].value = this.input.value; - -// for (var i = 0; i < this.output1.length; i++) -// simulationArea.simulationQueue.add(this.output1[i]); - -// } - -// customDraw() { - -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// ctx.beginPath(); -// moveTo(ctx, 0, this.yOff * 10 * (this.outputsize / 2 - 1) + 10 + 0.5 * this.xOff, xx, yy, this.direction); -// lineTo(ctx, 0, this.yOff * 5 * (this.outputsize - 1) + this.xOff, xx, yy, this.direction); -// ctx.stroke(); - -// ctx.beginPath(); -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(4); -// ctx.fillStyle = "white"; -// moveTo(ctx, -20 + this.xOff, -this.yOff * 10 * (this.outputsize / 2), xx, yy, this.direction); -// lineTo(ctx, -20 + this.xOff, 20 + this.yOff * 10 * (this.outputsize / 2 - 1), xx, yy, this.direction); -// lineTo(ctx, 20 - this.xOff, +this.yOff * 10 * (this.outputsize / 2 - 1) + this.xOff, xx, yy, this.direction); -// lineTo(ctx, 20 - this.xOff, -this.yOff * 10 * (this.outputsize / 2) - this.xOff + 20, xx, yy, this.direction); - -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) -// ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - - - -// ctx.beginPath(); -// ctx.fillStyle = "black"; -// ctx.textAlign = "center"; -// //[xFill,yFill] = rotate(xx + this.output1[i].x - 7, yy + this.output1[i].y + 2); -// ////console.log([xFill,yFill]) -// for (var i = 0; i < this.outputsize; i++) { -// if (this.direction == "LEFT") fillText(ctx, String(i), xx + this.output1[i].x - 7, yy + this.output1[i].y + 2, 10); -// else if (this.direction == "RIGHT") fillText(ctx, String(i), xx + this.output1[i].x + 7, yy + this.output1[i].y + 2, 10); -// else if (this.direction == "UP") fillText(ctx, String(i), xx + this.output1[i].x, yy + this.output1[i].y - 5, 10); -// else fillText(ctx, String(i), xx + this.output1[i].x, yy + this.output1[i].y + 10, 10); -// } -// ctx.fill(); -// } -// } - -// Demultiplexer.prototype.tooltipText = "DeMultiplexer ToolTip : Multiple outputs and a single line input."; - -// class Decoder extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "LEFT", bitWidth = 1) { - -// super(x, y, scope, dir, bitWidth); -// // this.controlSignalSize = controlSignalSize || parseInt(prompt("Enter control signal bitWidth"), 10); -// this.outputsize = 1 << this.bitWidth; -// this.xOff = 0; -// this.yOff = 1; -// if (this.bitWidth == 1) { -// this.xOff = 10; -// } -// if (this.bitWidth <= 3) { -// this.yOff = 2; -// } - -// // this.changeControlSignalSize = function(size) { -// // if (size == undefined || size < 1 || size > 32) return; -// // if (this.controlSignalSize == size) return; -// // var obj = new window[this.objectType](this.x, this.y, this.scope, this.direction, this.bitWidth, size); -// // this.cleanDelete(); -// // simulationArea.lastSelected = obj; -// // return obj; -// // } -// // this.mutableProperties = { -// // "controlSignalSize": { -// // name: "Control Signal Size", -// // type: "number", -// // max: "32", -// // min: "1", -// // func: "changeControlSignalSize", -// // }, -// // } -// this.newBitWidth = function (bitWidth) { -// // this.bitWidth = bitWidth; -// // for (var i = 0; i < this.inputSize; i++) { -// // this.outputs1[i].bitWidth = bitWidth -// // } -// // this.input.bitWidth = bitWidth; -// if (bitWidth == undefined || bitWidth < 1 || bitWidth > 32) return; -// if (this.bitWidth == bitWidth) return; -// var obj = new window[this.objectType](this.x, this.y, this.scope, this.direction, bitWidth); -// this.cleanDelete(); -// simulationArea.lastSelected = obj; -// return obj; -// } - -// this.setDimensions(20 - this.xOff, this.yOff * 5 * (this.outputsize)); -// this.rectangleObject = false; -// this.input = new Node(20 - this.xOff, 0, 0, this); - -// this.output1 = []; -// for (var i = 0; i < this.outputsize; i++) { -// var a = new Node(-20 + this.xOff, +this.yOff * 10 * (i - this.outputsize / 2) + 10, 1, this, 1); -// this.output1.push(a); -// } - -// // this.controlSignalInput = new Node(0,this.yOff * 10 * (this.outputsize / 2 - 1) +this.xOff + 10, 0, this, this.controlSignalSize,"Control Signal"); - - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth], -// nodes: { -// output1: this.output1.map(findNode), -// input: findNode(this.input), -// }, -// } -// return data; -// } - -// resolve() { - -// for (var i = 0; i < this.output1.length; i++) -// this.output1[i].value = 0; -// this.output1[this.input.value].value = 1; -// for (var i = 0; i < this.output1.length; i++) -// simulationArea.simulationQueue.add(this.output1[i]); - -// } - -// customDraw() { - -// ctx = simulationArea.context; - -// var xx = this.x; -// var yy = this.y; - -// // ctx.beginPath(); -// // moveTo(ctx, 0,this.yOff * 10 * (this.outputsize / 2 - 1) + 10 + 0.5 *this.xOff, xx, yy, this.direction); -// // lineTo(ctx, 0,this.yOff * 5 * (this.outputsize - 1) +this.xOff, xx, yy, this.direction); -// // ctx.stroke(); - -// ctx.beginPath(); -// ctx.strokeStyle = ("rgba(0,0,0,1)"); -// ctx.lineWidth = correctWidth(4); -// ctx.fillStyle = "white"; -// moveTo(ctx, -20 + this.xOff, -this.yOff * 10 * (this.outputsize / 2), xx, yy, this.direction); -// lineTo(ctx, -20 + this.xOff, 20 + this.yOff * 10 * (this.outputsize / 2 - 1), xx, yy, this.direction); -// lineTo(ctx, 20 - this.xOff, +this.yOff * 10 * (this.outputsize / 2 - 1) + this.xOff, xx, yy, this.direction); -// lineTo(ctx, 20 - this.xOff, -this.yOff * 10 * (this.outputsize / 2) - this.xOff + 20, xx, yy, this.direction); - -// ctx.closePath(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) -// ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - - - -// ctx.beginPath(); -// ctx.fillStyle = "black"; -// ctx.textAlign = "center"; -// //[xFill,yFill] = rotate(xx + this.output1[i].x - 7, yy + this.output1[i].y + 2); -// ////console.log([xFill,yFill]) -// for (var i = 0; i < this.outputsize; i++) { -// if (this.direction == "LEFT") fillText(ctx, String(i), xx + this.output1[i].x - 7, yy + this.output1[i].y + 2, 10); -// else if (this.direction == "RIGHT") fillText(ctx, String(i), xx + this.output1[i].x + 7, yy + this.output1[i].y + 2, 10); -// else if (this.direction == "UP") fillText(ctx, String(i), xx + this.output1[i].x, yy + this.output1[i].y - 5, 10); -// else fillText(ctx, String(i), xx + this.output1[i].x, yy + this.output1[i].y + 10, 10); -// } -// ctx.fill(); -// } -// } - -// Decoder.prototype.tooltipText = "Decoder ToolTip : Converts coded inputs into coded outputs."; - -// class Flag extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1, identifier) { - -// super(x, y, scope, dir, bitWidth); -// this.setWidth(60); -// this.setHeight(20); -// this.rectangleObject = false; -// this.directionFixed = true; -// this.orientationFixed = false; -// this.identifier = identifier || ("F" + this.scope.Flag.length); -// this.plotValues = []; - -// this.xSize = 10; - -// this.inp1 = new Node(40, 0, 0, this); -// } - -// setPlotValue() { -// var time = plotArea.stopWatch.ElapsedMilliseconds; - -// // //console.log("DEB:",time); -// if (this.plotValues.length && this.plotValues[this.plotValues.length - 1][0] == time) -// this.plotValues.pop(); - -// if (this.plotValues.length == 0) { -// this.plotValues.push([time, this.inp1.value]); -// return; -// } - -// if (this.plotValues[this.plotValues.length - 1][1] == this.inp1.value) -// return; -// else -// this.plotValues.push([time, this.inp1.value]); -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth], -// nodes: { -// inp1: findNode(this.inp1), -// }, -// values: { -// identifier: this.identifier -// } -// } -// return data; -// } - -// setIdentifier(id = "") { -// if (id.length == 0) return; -// this.identifier = id; -// var len = this.identifier.length; -// if (len == 1) this.xSize = 20; -// else if (len > 1 && len < 4) this.xSize = 10; -// else this.xSize = 0; -// } - -// customDraw() { -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = "grey"; -// ctx.fillStyle = "#fcfcfc"; -// ctx.lineWidth = correctWidth(1); -// var xx = this.x; -// var yy = this.y; - - -// rect2(ctx, -50 + this.xSize, -20, 100 - 2 * this.xSize, 40, xx, yy, "RIGHT"); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - -// ctx.font = "14px Georgia"; -// this.xOff = ctx.measureText(this.identifier).width; - -// ctx.beginPath(); -// rect2(ctx, -40 + this.xSize, -12, this.xOff + 10, 25, xx, yy, "RIGHT"); -// ctx.fillStyle = "#eee" -// ctx.strokeStyle = "#ccc"; -// ctx.fill(); -// ctx.stroke(); - -// ctx.beginPath(); -// ctx.textAlign = "center"; -// ctx.fillStyle = "black"; -// fillText(ctx, this.identifier, xx - 35 + this.xOff / 2 + this.xSize, yy + 5, 14); -// ctx.fill(); - -// ctx.beginPath(); -// ctx.font = "30px Georgia"; -// ctx.textAlign = "center"; -// ctx.fillStyle = ["blue", "red"][+(this.inp1.value == undefined)]; -// if (this.inp1.value !== undefined) -// fillText(ctx, this.inp1.value.toString(16), xx + 35 - this.xSize, yy + 8, 25); -// else -// fillText(ctx, "x", xx + 35 - this.xSize, yy + 8, 25); -// ctx.fill(); - -// } - -// newDirection(dir) { -// if (dir == this.direction) return; -// this.direction = dir; -// this.inp1.refresh(); -// if (dir == "RIGHT" || dir == "LEFT") { -// this.inp1.leftx = 50 - this.xSize; -// } else if (dir == "UP") { -// this.inp1.leftx = 20; -// } else { -// this.inp1.leftx = 20; -// } -// // if(this.direction=="LEFT" || this.direction=="RIGHT") this.inp1.leftx=50-this.xSize; -// // this.inp1.refresh(); - -// this.inp1.refresh(); -// } -// } - -// Flag.prototype.tooltipText = "FLag ToolTip: Use this for debugging and plotting." -// Flag.prototype.mutableProperties = { -// "identifier": { -// name: "Debug Flag identifier", -// type: "text", -// maxlength: "5", -// func: "setIdentifier", -// }, -// } - -// class MSB extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { - -// super(x, y, scope, dir, bitWidth); -// // this.setDimensions(20, 20); -// this.leftDimensionX = 10; -// this.rightDimensionX = 20; -// this.setHeight(30); -// this.directionFixed = true; -// this.bitWidth = bitWidth || parseInt(prompt("Enter bitWidth"), 10); -// this.rectangleObject = false; -// this.inputSize = 1 << this.bitWidth; - -// this.inp1 = new Node(-10, 0, 0, this, this.inputSize); -// this.output1 = new Node(20, 0, 1, this, this.bitWidth); -// this.enable = new Node(20, 20, 1, this, 1); - - - -// } - -// customSave() { -// var data = { - -// nodes: { -// inp1: findNode(this.inp1), -// output1: findNode(this.output1), -// enable: findNode(this.enable) -// }, -// constructorParamaters: [this.direction, this.bitWidth], -// } -// return data; -// } - -// newBitWidth(bitWidth) { -// // this.inputSize = 1 << bitWidth -// this.inputSize = bitWidth -// this.inp1.bitWidth = this.inputSize; -// this.bitWidth = bitWidth; -// this.output1.bitWidth = bitWidth; -// } - -// resolve() { - -// var inp = this.inp1.value; -// this.output1.value = (dec2bin(inp).length) - 1 -// simulationArea.simulationQueue.add(this.output1); -// if (inp != 0) { -// this.enable.value = 1; -// } else { -// this.enable.value = 0; -// } -// simulationArea.simulationQueue.add(this.enable); -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = "black"; -// ctx.fillStyle = "white"; -// ctx.lineWidth = correctWidth(3); -// var xx = this.x; -// var yy = this.y; -// rect(ctx, xx - 10, yy - 30, 30, 60); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - -// ctx.beginPath(); -// ctx.fillStyle = "black"; -// ctx.textAlign = "center"; -// fillText(ctx, "MSB", xx + 6, yy - 12, 10); -// fillText(ctx, "EN", xx + this.enable.x - 12, yy + this.enable.y + 3, 8); -// ctx.fill(); - -// ctx.beginPath(); -// ctx.fillStyle = "green"; -// ctx.textAlign = "center"; -// if (this.output1.value != undefined) { -// fillText(ctx, this.output1.value, xx + 5, yy + 14, 13); -// } -// ctx.stroke(); -// ctx.fill(); -// } -// } - -// MSB.prototype.tooltipText = "MSB ToolTip : The most significant bit or the high-order bit."; - -// class LSB extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { - -// super(x, y, scope, dir, bitWidth); -// this.leftDimensionX = 10; -// this.rightDimensionX = 20; -// this.setHeight(30); -// this.directionFixed = true; -// this.bitWidth = bitWidth || parseInt(prompt("Enter bitWidth"), 10); -// this.rectangleObject = false; -// this.inputSize = 1 << this.bitWidth; - -// this.inp1 = new Node(-10, 0, 0, this, this.inputSize); -// this.output1 = new Node(20, 0, 1, this, this.bitWidth); -// this.enable = new Node(20, 20, 1, this, 1); - - - -// } - -// customSave() { -// var data = { - -// nodes: { -// inp1: findNode(this.inp1), -// output1: findNode(this.output1), -// enable: findNode(this.enable) -// }, -// constructorParamaters: [this.direction, this.bitWidth], -// } -// return data; -// } - -// newBitWidth(bitWidth) { -// // this.inputSize = 1 << bitWidth -// this.inputSize = bitWidth -// this.inp1.bitWidth = this.inputSize; -// this.bitWidth = bitWidth; -// this.output1.bitWidth = bitWidth; -// } - -// resolve() { - -// var inp = dec2bin(this.inp1.value); -// var out = 0; -// for (var i = inp.length - 1; i >= 0; i--) { -// if (inp[i] == 1) { -// out = inp.length - 1 - i; -// break; -// } - -// } -// this.output1.value = out; -// simulationArea.simulationQueue.add(this.output1); -// if (inp != 0) { -// this.enable.value = 1; -// } else { -// this.enable.value = 0; -// } -// simulationArea.simulationQueue.add(this.enable); -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = "black"; -// ctx.fillStyle = "white"; -// ctx.lineWidth = correctWidth(3); -// var xx = this.x; -// var yy = this.y; -// rect(ctx, xx - 10, yy - 30, 30, 60); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - -// ctx.beginPath(); -// ctx.fillStyle = "black"; -// ctx.textAlign = "center"; -// fillText(ctx, "LSB", xx + 6, yy - 12, 10); -// fillText(ctx, "EN", xx + this.enable.x - 12, yy + this.enable.y + 3, 8); -// ctx.fill(); - -// ctx.beginPath(); -// ctx.fillStyle = "green"; -// ctx.textAlign = "center"; -// if (this.output1.value != undefined) { -// fillText(ctx, this.output1.value, xx + 5, yy + 14, 13); -// } -// ctx.stroke(); -// ctx.fill(); -// } -// } - -// LSB.prototype.tooltipText = "LSB ToolTip : The least significant bit or the low-order bit."; - -// class PriorityEncoder extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { - -// super(x, y, scope, dir, bitWidth); -// this.bitWidth = bitWidth || parseInt(prompt("Enter bitWidth"), 10); -// this.inputSize = 1 << this.bitWidth; - -// this.yOff = 1; -// if (this.bitWidth <= 3) { -// this.yOff = 2; -// } - -// this.setDimensions(20, this.yOff * 5 * (this.inputSize)); -// this.directionFixed = true; -// this.rectangleObject = false; - -// this.inp1 = []; -// for (var i = 0; i < this.inputSize; i++) { -// var a = new Node(-10, +this.yOff * 10 * (i - this.inputSize / 2) + 10, 0, this, 1); -// this.inp1.push(a); -// } - -// this.output1 = []; -// for (var i = 0; i < this.bitWidth; i++) { -// var a = new Node(30, +2 * 10 * (i - this.bitWidth / 2) + 10, 1, this, 1); -// this.output1.push(a); -// } - -// this.enable = new Node(10, 20 + this.inp1[this.inputSize - 1].y, 1, this, 1); - - -// } - -// customSave() { -// var data = { - -// nodes: { -// inp1: this.inp1.map(findNode), -// output1: this.output1.map(findNode), -// enable: findNode(this.enable) -// }, -// constructorParamaters: [this.direction, this.bitWidth], -// } -// return data; -// } - -// newBitWidth(bitWidth) { -// if (bitWidth == undefined || bitWidth < 1 || bitWidth > 32) return; -// if (this.bitWidth == bitWidth) return; - -// this.bitWidth = bitWidth; -// var obj = new window[this.objectType](this.x, this.y, this.scope, this.direction, this.bitWidth); -// this.inputSize = 1 << bitWidth; - -// this.cleanDelete(); -// simulationArea.lastSelected = obj; -// return obj; -// } - -// resolve() { -// var out = 0; -// var temp = 0; -// for (var i = this.inputSize - 1; i >= 0; i--) { -// if (this.inp1[i].value == 1) { -// out = dec2bin(i); -// break; -// } -// } -// temp = out; - -// if (out.length != undefined) { -// this.enable.value = 1; -// } else { -// this.enable.value = 0; -// } -// simulationArea.simulationQueue.add(this.enable); - -// if (temp.length == undefined) { -// temp = "0"; -// for (var i = 0; i < this.bitWidth - 1; i++) { -// temp = "0" + temp; -// } -// } - -// if (temp.length != this.bitWidth) { -// for (var i = temp.length; i < this.bitWidth; i++) { -// temp = "0" + temp; -// } -// } - -// for (var i = this.bitWidth - 1; i >= 0; i--) { -// this.output1[this.bitWidth - 1 - i].value = Number(temp[i]); -// simulationArea.simulationQueue.add(this.output1[this.bitWidth - 1 - i]); -// } -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = "black"; -// ctx.fillStyle = "white"; -// ctx.lineWidth = correctWidth(3); -// var xx = this.x; -// var yy = this.y; -// if (this.bitWidth <= 3) -// rect(ctx, xx - 10, yy - 10 - this.yOff * 5 * (this.inputSize), 40, 20 * (this.inputSize + 1)); -// else -// rect(ctx, xx - 10, yy - 10 - this.yOff * 5 * (this.inputSize), 40, 10 * (this.inputSize + 3)); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - -// ctx.beginPath(); -// ctx.fillStyle = "black"; -// ctx.textAlign = "center"; -// for (var i = 0; i < this.inputSize; i++) { -// fillText(ctx, String(i), xx, yy + this.inp1[i].y + 2, 10); -// } -// for (var i = 0; i < this.bitWidth; i++) { -// fillText(ctx, String(i), xx + this.output1[0].x - 10, yy + this.output1[i].y + 2, 10); -// } -// fillText(ctx, "EN", xx + this.enable.x, yy + this.enable.y - 5, 10); -// ctx.fill(); - -// } -// } - -// PriorityEncoder.prototype.tooltipText = "Priority Encoder ToolTip : Compresses binary inputs into a smaller number of outputs."; - -// class Tunnel extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "LEFT", bitWidth = 1, identifier) { - -// super(x, y, scope, dir, bitWidth); -// this.rectangleObject = false; -// this.centerElement = true; - -// this.xSize = 10; - -// this.plotValues = []; -// this.inp1 = new Node(0, 0, 0, this); -// this.setIdentifier(identifier || "T"); -// this.setBounds(); - -// } - -// newDirection(dir) { -// if (this.direction == dir) return; -// this.direction = dir; -// this.setBounds(); -// } - -// setBounds() { - -// var xRotate = 0; -// var yRotate = 0; -// if (this.direction == "LEFT") { -// xRotate = 0; -// yRotate = 0; -// } else if (this.direction == "RIGHT") { -// xRotate = 120 - this.xSize; -// yRotate = 0; -// } else if (this.direction == "UP") { -// xRotate = 60 - this.xSize / 2; -// yRotate = -20; -// } else { -// xRotate = 60 - this.xSize / 2; -// yRotate = 20; -// } - -// this.leftDimensionX = Math.abs(-120 + xRotate + this.xSize); -// this.upDimensionY = Math.abs(-20 + yRotate); -// this.rightDimensionX = Math.abs(xRotate) -// this.downDimensionY = Math.abs(20 + yRotate); -// console.log(this.leftDimensionX, this.upDimensionY, this.rightDimensionX, this.downDimensionY); - -// // rect2(ctx, -120 + xRotate + this.xSize, -20 + yRotate, 120 - this.xSize, 40, xx, yy, "RIGHT"); - - -// } - -// setTunnelValue(val) { -// this.inp1.value = val; -// for (var i = 0; i < this.inp1.connections.length; i++) { -// if (this.inp1.connections[i].value != val) { -// this.inp1.connections[i].value = val; -// simulationArea.simulationQueue.add(this.inp1.connections[i]); -// } -// } -// } - -// resolve() { -// for (var i = 0; i < this.scope.tunnelList[this.identifier].length; i++) { -// if (this.scope.tunnelList[this.identifier][i].inp1.value != this.inp1.value) { -// this.scope.tunnelList[this.identifier][i].setTunnelValue(this.inp1.value); -// } -// } -// } - -// updateScope(scope) { -// this.scope = scope; -// this.inp1.updateScope(scope); -// this.setIdentifier(this.identifier); -// //console.log("ShouldWork!"); -// } - -// setPlotValue() { -// var time = plotArea.stopWatch.ElapsedMilliseconds; -// if (this.plotValues.length && this.plotValues[this.plotValues.length - 1][0] == time) -// this.plotValues.pop(); - -// if (this.plotValues.length == 0) { -// this.plotValues.push([time, this.inp1.value]); -// return; -// } - -// if (this.plotValues[this.plotValues.length - 1][1] == this.inp1.value) -// return; -// else -// this.plotValues.push([time, this.inp1.value]); -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth, this.identifier], -// nodes: { -// inp1: findNode(this.inp1), -// }, -// values: { -// identifier: this.identifier -// } -// } -// return data; -// } - -// setIdentifier(id = "") { -// if (id.length == 0) return; -// if (this.scope.tunnelList[this.identifier]) this.scope.tunnelList[this.identifier].clean(this); -// this.identifier = id; -// if (this.scope.tunnelList[this.identifier]) this.scope.tunnelList[this.identifier].push(this); -// else this.scope.tunnelList[this.identifier] = [this]; - -// var len = this.identifier.length; -// if (len == 1) this.xSize = 40; -// else if (len > 1 && len < 4) this.xSize = 20; -// else this.xSize = 0; - -// this.setBounds(); -// } - -// delete() { -// this.scope.Tunnel.clean(this); -// this.scope.tunnelList[this.identifier].clean(this); -// super.delete(); -// } - -// customDraw() { -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = "grey"; -// ctx.fillStyle = "#fcfcfc"; -// ctx.lineWidth = correctWidth(1); -// var xx = this.x; -// var yy = this.y; - -// var xRotate = 0; -// var yRotate = 0; -// if (this.direction == "LEFT") { -// xRotate = 0; -// yRotate = 0; -// } else if (this.direction == "RIGHT") { -// xRotate = 120 - this.xSize; -// yRotate = 0; -// } else if (this.direction == "UP") { -// xRotate = 60 - this.xSize / 2; -// yRotate = -20; -// } else { -// xRotate = 60 - this.xSize / 2; -// yRotate = 20; -// } - -// rect2(ctx, -120 + xRotate + this.xSize, -20 + yRotate, 120 - this.xSize, 40, xx, yy, "RIGHT"); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) -// ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - -// ctx.font = "14px Georgia"; -// this.xOff = ctx.measureText(this.identifier).width; -// ctx.beginPath(); -// rect2(ctx, -105 + xRotate + this.xSize, -11 + yRotate, this.xOff + 10, 23, xx, yy, "RIGHT"); -// ctx.fillStyle = "#eee" -// ctx.strokeStyle = "#ccc"; -// ctx.fill(); -// ctx.stroke(); - -// ctx.beginPath(); -// ctx.textAlign = "center"; -// ctx.fillStyle = "black"; -// fillText(ctx, this.identifier, xx - 100 + this.xOff / 2 + xRotate + this.xSize, yy + 6 + yRotate, 14); -// ctx.fill(); - -// ctx.beginPath(); -// ctx.font = "30px Georgia"; -// ctx.textAlign = "center"; -// ctx.fillStyle = ["blue", "red"][+(this.inp1.value == undefined)]; -// if (this.inp1.value !== undefined) -// fillText(ctx, this.inp1.value.toString(16), xx - 23 + xRotate, yy + 8 + yRotate, 25); -// else -// fillText(ctx, "x", xx - 23 + xRotate, yy + 8 + yRotate, 25); -// ctx.fill(); -// } -// } - -// Tunnel.prototype.tooltipText = "Tunnel ToolTip : Tunnel Selected."; -// Tunnel.prototype.overrideDirectionRotation = true; -// Tunnel.prototype.mutableProperties = { -// "identifier": { -// name: "Debug Flag identifier", -// type: "text", -// maxlength: "5", -// func: "setIdentifier", -// }, -// } - -// class ALU extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT", bitWidth = 1) { -// // //console.log("HIT"); -// // //console.log(x,y,scope,dir,bitWidth,controlSignalSize); -// super(x, y, scope, dir, bitWidth); -// this.message = "ALU"; - -// this.setDimensions(30, 40); -// this.rectangleObject = false; - -// this.inp1 = new Node(-30, -30, 0, this, this.bitwidth, "A"); -// this.inp2 = new Node(-30, 30, 0, this, this.bitwidth, "B"); - -// this.controlSignalInput = new Node(-10, -40, 0, this, 3, "Ctrl"); -// this.carryOut = new Node(-10, 40, 1, this, 1, "Cout"); -// this.output = new Node(30, 0, 1, this, this.bitwidth, "Out"); - -// } - -// newBitWidth(bitWidth) { -// this.bitWidth = bitWidth; -// this.inp1.bitWidth = bitWidth; -// this.inp2.bitWidth = bitWidth; -// this.output.bitWidth = bitWidth; -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction, this.bitWidth], -// nodes: { -// inp1: findNode(this.inp1), -// inp2: findNode(this.inp2), -// output: findNode(this.output), -// carryOut: findNode(this.carryOut), -// controlSignalInput: findNode(this.controlSignalInput) -// }, -// } -// return data; -// } - -// customDraw() { -// ctx = simulationArea.context; -// var xx = this.x; -// var yy = this.y; -// ctx.strokeStyle = "black"; -// ctx.fillStyle = "white"; -// ctx.lineWidth = correctWidth(3); -// ctx.beginPath(); -// moveTo(ctx, 30, 10, xx, yy, this.direction); -// lineTo(ctx, 30, -10, xx, yy, this.direction); -// lineTo(ctx, 10, -40, xx, yy, this.direction); -// lineTo(ctx, -30, -40, xx, yy, this.direction); -// lineTo(ctx, -30, -20, xx, yy, this.direction); -// lineTo(ctx, -20, -10, xx, yy, this.direction); -// lineTo(ctx, -20, 10, xx, yy, this.direction); -// lineTo(ctx, -30, 20, xx, yy, this.direction); -// lineTo(ctx, -30, 40, xx, yy, this.direction); -// lineTo(ctx, 10, 40, xx, yy, this.direction); -// lineTo(ctx, 30, 10, xx, yy, this.direction); -// ctx.closePath(); -// ctx.stroke(); - -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) -// ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); -// ctx.stroke(); - -// ctx.beginPath(); -// ctx.fillStyle = "Black"; -// ctx.textAlign = "center" - -// fillText4(ctx, "B", -23, 30, xx, yy, this.direction, 6); -// fillText4(ctx, "A", -23, -30, xx, yy, this.direction, 6); -// fillText4(ctx, "CTR", -10, -30, xx, yy, this.direction, 6); -// fillText4(ctx, "Carry", -10, 30, xx, yy, this.direction, 6); -// fillText4(ctx, "Ans", 20, 0, xx, yy, this.direction, 6); -// ctx.fill(); -// ctx.beginPath(); -// ctx.fillStyle = "DarkGreen"; -// fillText4(ctx, this.message, 0, 0, xx, yy, this.direction, 12); -// ctx.fill(); - -// } - -// resolve() { -// if (this.controlSignalInput.value == 0) { -// this.output.value = ((this.inp1.value) & (this.inp2.value)); -// simulationArea.simulationQueue.add(this.output); -// this.carryOut.value = 0; -// simulationArea.simulationQueue.add(this.carryOut); -// this.message = "A&B"; -// } else if (this.controlSignalInput.value == 1) { -// this.output.value = ((this.inp1.value) | (this.inp2.value)); - -// simulationArea.simulationQueue.add(this.output); -// this.carryOut.value = 0; -// simulationArea.simulationQueue.add(this.carryOut); -// this.message = "A|B"; -// } else if (this.controlSignalInput.value == 2) { -// var sum = this.inp1.value + this.inp2.value; -// this.output.value = ((sum) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); -// this.carryOut.value = +((sum >>> (this.bitWidth)) !== 0); -// simulationArea.simulationQueue.add(this.carryOut); -// simulationArea.simulationQueue.add(this.output); -// this.message = "A+B"; -// } else if (this.controlSignalInput.value == 3) { -// this.message = "ALU"; -// return; -// } else if (this.controlSignalInput.value == 4) { -// this.message = "A&~B"; -// this.output.value = ((this.inp1.value) & this.flipBits(this.inp2.value)); -// simulationArea.simulationQueue.add(this.output); -// this.carryOut.value = 0; -// simulationArea.simulationQueue.add(this.carryOut); -// } else if (this.controlSignalInput.value == 5) { -// this.message = "A|~B"; -// this.output.value = ((this.inp1.value) | this.flipBits(this.inp2.value)); -// simulationArea.simulationQueue.add(this.output); -// this.carryOut.value = 0; -// simulationArea.simulationQueue.add(this.carryOut); -// } else if (this.controlSignalInput.value == 6) { -// this.message = "A-B"; -// this.output.value = ((this.inp1.value - this.inp2.value) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); -// simulationArea.simulationQueue.add(this.output); -// this.carryOut.value = 0; -// simulationArea.simulationQueue.add(this.carryOut); -// } else if (this.controlSignalInput.value == 7) { -// this.message = "A 1000) return; -// if (this.rows == size) return; -// this.rows = parseInt(size) -// this.setSize() -// return this; -// } - -// changeColSize(size) { -// if (size == undefined || size < 5 || size > 1000) return; -// if (this.cols == size) return; -// this.cols = parseInt(size); -// this.setSize() -// return this; -// } - -// keyDown3(dir) { -// //console.log(dir) -// if (dir == "ArrowRight") -// this.changeColSize(this.cols + 2) -// if (dir == "ArrowLeft") -// this.changeColSize(this.cols - 2) -// if (dir == "ArrowDown") -// this.changeRowSize(this.rows + 2) -// if (dir == "ArrowUp") -// this.changeRowSize(this.rows - 2) -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.rows, this.cols], -// } -// return data; -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.beginPath(); -// ctx.strokeStyle = "rgba(0,0,0,1)"; -// ctx.setLineDash([5 * globalScope.scale, 5 * globalScope.scale]) -// ctx.lineWidth = correctWidth(1.5); -// var xx = this.x; -// var yy = this.y; -// rect(ctx, xx, yy, this.elementWidth, this.elementHeight); -// ctx.stroke(); - -// if (simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) { -// ctx.fillStyle = "rgba(255, 255, 32,0.1)"; -// ctx.fill(); -// } -// ctx.setLineDash([]) - - -// } - -// setSize() { -// this.elementWidth = this.cols * 10; -// this.elementHeight = this.rows * 10; -// this.upDimensionY = 0; -// this.leftDimensionX = 0; -// this.rightDimensionX = this.elementWidth; -// this.downDimensionY = this.elementHeight; -// } -// } - -// Rectangle.prototype.tooltipText = "Rectangle ToolTip : Used to Box the Circuit or area you want to highlight."; -// Rectangle.prototype.mutableProperties = { -// "cols": { -// name: "Columns", -// type: "number", -// max: "1000", -// min: "5", -// func: "changeColSize", -// }, -// "rows": { -// name: "Rows", -// type: "number", -// max: "1000", -// min: "5", -// func: "changeRowSize", -// } -// } - -// class Arrow extends CircuitElement { -// constructor(x, y, scope = globalScope, dir = "RIGHT") { - -// super(x, y, scope, dir, 8); -// this.rectangleObject = false; -// this.fixedBitWidth = true -// this.setDimensions(30, 20); - -// } - -// customSave() { -// var data = { -// constructorParamaters: [this.direction], -// } -// return data; -// } - -// customDraw() { - -// ctx = simulationArea.context; -// ctx.lineWidth = correctWidth(3); -// var xx = this.x; -// var yy = this.y; -// ctx.strokeStyle = "red"; -// ctx.fillStyle = "white"; - -// ctx.beginPath(); - -// moveTo(ctx, -30, -3, xx, yy, this.direction); -// lineTo(ctx, 10, -3, xx, yy, this.direction); -// lineTo(ctx, 10, -15, xx, yy, this.direction); -// lineTo(ctx, 30, 0, xx, yy, this.direction); -// lineTo(ctx, 10, 15, xx, yy, this.direction); -// lineTo(ctx, 10, 3, xx, yy, this.direction); -// lineTo(ctx, -30, 3, xx, yy, this.direction); -// ctx.closePath() -// ctx.stroke(); -// if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)"; -// ctx.fill(); - -// } -// } - -// Arrow.prototype.tooltipText = "Arrow ToolTip : Arrow Selected."; diff --git a/src/modules.js b/src/modules.js new file mode 100755 index 0000000..646ed09 --- /dev/null +++ b/src/modules.js @@ -0,0 +1,154 @@ +/* eslint-disable import/no-cycle */ +import AndGate from './modules/AndGate'; +import NandGate from './modules/NandGate'; +import Multiplexer from './modules/Multiplexer'; +import XorGate from './modules/XorGate'; +import XnorGate from './modules/XnorGate'; +import SevenSegDisplay from './modules/SevenSegDisplay'; +import SixteenSegDisplay from './modules/SixteenSegDisplay'; +import HexDisplay from './modules/HexDisplay'; +import OrGate from './modules/OrGate'; +import Stepper from './modules/Stepper'; +import NotGate from './modules/NotGate'; +import Text from './modules/Text'; +import TriState from './modules/TriState'; +import Buffer from './modules/Buffer'; +import ControlledInverter from './modules/ControlledInverter'; +import Adder from './modules/Adder'; +import TwoComplement from './modules/TwoComplement'; +import Splitter from './modules/Splitter'; +import Ground from './modules/Ground'; +import Power from './modules/Power'; +import Input from './modules/Input'; +import Output from './modules/Output'; +import BitSelector from './modules/BitSelector'; +import ConstantVal from './modules/ConstantVal'; +import NorGate from './modules/NorGate'; +import DigitalLed from './modules/DigitalLed'; +import VariableLed from './modules/VariableLed'; +import Button from './modules/Button'; +import RGBLed from './modules/RGBLed'; +import SquareRGBLed from './modules/SquareRGBLed'; +import Demultiplexer from './modules/Demultiplexer'; +import Decoder from './modules/Decoder'; +import Flag from './modules/Flag'; +import MSB from './modules/MSB'; +import LSB from './modules/LSB'; +import PriorityEncoder from './modules/PriorityEncoder'; +import Tunnel from './modules/Tunnel'; +import ALU from './modules/ALU'; +import Rectangle from './modules/Rectangle'; +import Arrow from './modules/Arrow'; +import Counter from './modules/Counter'; +import Random from './modules/Random'; +import RGBLedMatrix from './modules/RGBLedMatrix'; +import simulationArea from './simulationArea'; +import TflipFlop from './sequential/TflipFlop'; +import DflipFlop from './sequential/DflipFlop'; +import Dlatch from './sequential/Dlatch'; +import SRflipFlop from './sequential/SRflipFlop'; +import JKflipFlop from './sequential/JKflipFlop'; +import TTY from './sequential/TTY'; +import Keyboard from './sequential/Keyboard'; +import Clock from './sequential/Clock'; +import RAM from './sequential/RAM'; +import EEPROM from './sequential/EEPROM'; +import Rom from './sequential/Rom'; +import TB_Input from './testbench/testbenchInput'; +import TB_Output from './testbench/testbenchOutput'; +import ForceGate from './testbench/ForceGate'; + +export function getNextPosition(x = 0, scope = globalScope) { + let possibleY = 20; + const done = {}; + for (let i = 0; i < scope.Input.length - 1; i++) { + if (scope.Input[i].layoutProperties.x === x) { done[scope.Input[i].layoutProperties.y] = 1; } + } + for (let i = 0; i < scope.Output.length; i++) { + if (scope.Output[i].layoutProperties.x === x) { done[scope.Output[i].layoutProperties.y] = 1; } + } + while (done[possibleY] || done[possibleY + 10] || done[possibleY - 10]) { possibleY += 10; } + const height = possibleY + 20; + if (height > scope.layout.height) { + const oldHeight = scope.layout.height; + scope.layout.height = height; + for (let i = 0; i < scope.Input.length; i++) { + if (scope.Input[i].layoutProperties.y === oldHeight) { scope.Input[i].layoutProperties.y = scope.layout.height; } + } + for (let i = 0; i < scope.Output.length; i++) { + if (scope.Output[i].layoutProperties.y === oldHeight) { scope.Output[i].layoutProperties.y = scope.layout.height; } + } + } + return possibleY; +} + +const modules = { + AndGate, + Random, + NandGate, + Counter, + Multiplexer, + XorGate, + XnorGate, + SevenSegDisplay, + SixteenSegDisplay, + HexDisplay, + OrGate, + Stepper, + NotGate, + Text, + TriState, + Buffer, + ControlledInverter, + Adder, + TwoComplement, + Splitter, + Ground, + Power, + Input, + Output, + BitSelector, + ConstantVal, + NorGate, + DigitalLed, + VariableLed, + Button, + RGBLed, + SquareRGBLed, + Demultiplexer, + Decoder, + Flag, + MSB, + LSB, + PriorityEncoder, + Tunnel, + ALU, + Rectangle, + Arrow, + RGBLedMatrix, + TflipFlop, + DflipFlop, + Dlatch, + SRflipFlop, + JKflipFlop, + TTY, + Keyboard, + Clock, + Rom, + EEPROM, + RAM, + TB_Input, + TB_Output, + ForceGate, +}; +export default modules; +export function changeInputSize(size) { + if (size == undefined || size < 2 || size > 10) return; + if (this.inputSize == size) return; + size = parseInt(size, 10); + const obj = new modules[this.objectType](this.x, this.y, this.scope, this.direction, size, this.bitWidth); + this.delete(); + simulationArea.lastSelected = obj; + return obj; + // showProperties(obj); +} diff --git a/src/modules/ALU.js b/src/modules/ALU.js new file mode 100755 index 0000000..48ac4ce --- /dev/null +++ b/src/modules/ALU.js @@ -0,0 +1,188 @@ +/* eslint-disable no-bitwise */ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText4, +} from '../canvasApi'; + +/** + * @class + * ALU + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class ALU extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + // //console.log("HIT"); + // //console.log(x,y,scope,dir,bitWidth,controlSignalSize); + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['ALU'].push(this); + */ + this.message = 'ALU'; + + this.setDimensions(30, 40); + this.rectangleObject = false; + + this.inp1 = new Node(-30, -30, 0, this, this.bitwidth, 'A'); + this.inp2 = new Node(-30, 30, 0, this, this.bitwidth, 'B'); + + this.controlSignalInput = new Node(-10, -40, 0, this, 3, 'Ctrl'); + this.carryOut = new Node(-10, 40, 1, this, 1, 'Cout'); + this.output = new Node(30, 0, 1, this, this.bitwidth, 'Out'); + } + + /** + * @memberof ALU + * function to change bitwidth of the element + * @param {number} bitWidth - new bitwidth + */ + newBitWidth(bitWidth) { + this.bitWidth = bitWidth; + this.inp1.bitWidth = bitWidth; + this.inp2.bitWidth = bitWidth; + this.output.bitWidth = bitWidth; + } + + /** + * @memberof ALU + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth], + nodes: { + inp1: findNode(this.inp1), + inp2: findNode(this.inp2), + output: findNode(this.output), + carryOut: findNode(this.carryOut), + controlSignalInput: findNode(this.controlSignalInput), + }, + }; + return data; + } + + /** + * @memberof ALU + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + ctx.beginPath(); + moveTo(ctx, 30, 10, xx, yy, this.direction); + lineTo(ctx, 30, -10, xx, yy, this.direction); + lineTo(ctx, 10, -40, xx, yy, this.direction); + lineTo(ctx, -30, -40, xx, yy, this.direction); + lineTo(ctx, -30, -20, xx, yy, this.direction); + lineTo(ctx, -20, -10, xx, yy, this.direction); + lineTo(ctx, -20, 10, xx, yy, this.direction); + lineTo(ctx, -30, 20, xx, yy, this.direction); + lineTo(ctx, -30, 40, xx, yy, this.direction); + lineTo(ctx, 10, 40, xx, yy, this.direction); + lineTo(ctx, 30, 10, xx, yy, this.direction); + ctx.closePath(); + ctx.stroke(); + + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) { ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; } + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.fillStyle = 'Black'; + ctx.textAlign = 'center'; + + fillText4(ctx, 'B', -23, 30, xx, yy, this.direction, 6); + fillText4(ctx, 'A', -23, -30, xx, yy, this.direction, 6); + fillText4(ctx, 'CTR', -10, -30, xx, yy, this.direction, 6); + fillText4(ctx, 'Carry', -10, 30, xx, yy, this.direction, 6); + fillText4(ctx, 'Ans', 20, 0, xx, yy, this.direction, 6); + ctx.fill(); + ctx.beginPath(); + ctx.fillStyle = 'DarkGreen'; + fillText4(ctx, this.message, 0, 0, xx, yy, this.direction, 12); + ctx.fill(); + } + + /** + * @memberof ALU + * resolve output values based on inputData + */ + resolve() { + if (this.controlSignalInput.value === 0) { + this.output.value = ((this.inp1.value) & (this.inp2.value)); + simulationArea.simulationQueue.add(this.output); + this.carryOut.value = 0; + simulationArea.simulationQueue.add(this.carryOut); + this.message = 'A&B'; + } else if (this.controlSignalInput.value === 1) { + this.output.value = ((this.inp1.value) | (this.inp2.value)); + + simulationArea.simulationQueue.add(this.output); + this.carryOut.value = 0; + simulationArea.simulationQueue.add(this.carryOut); + this.message = 'A|B'; + } else if (this.controlSignalInput.value === 2) { + const sum = this.inp1.value + this.inp2.value; + this.output.value = ((sum) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); + this.carryOut.value = +((sum >>> (this.bitWidth)) !== 0); + simulationArea.simulationQueue.add(this.carryOut); + simulationArea.simulationQueue.add(this.output); + this.message = 'A+B'; + } else if (this.controlSignalInput.value === 3) { + this.message = 'ALU'; + } else if (this.controlSignalInput.value === 4) { + this.message = 'A&~B'; + this.output.value = ((this.inp1.value) & this.flipBits(this.inp2.value)); + simulationArea.simulationQueue.add(this.output); + this.carryOut.value = 0; + simulationArea.simulationQueue.add(this.carryOut); + } else if (this.controlSignalInput.value === 5) { + this.message = 'A|~B'; + this.output.value = ((this.inp1.value) | this.flipBits(this.inp2.value)); + simulationArea.simulationQueue.add(this.output); + this.carryOut.value = 0; + simulationArea.simulationQueue.add(this.carryOut); + } else if (this.controlSignalInput.value === 6) { + this.message = 'A-B'; + this.output.value = ((this.inp1.value - this.inp2.value) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); + simulationArea.simulationQueue.add(this.output); + this.carryOut.value = 0; + simulationArea.simulationQueue.add(this.carryOut); + } else if (this.controlSignalInput.value === 7) { + this.message = 'A>> (32 - this.bitWidth); + this.carryOut.value = +((sum >>> (this.bitWidth)) !== 0); + simulationArea.simulationQueue.add(this.carryOut); + simulationArea.simulationQueue.add(this.sum); + } +} + +/** + * @memberof Adder + * Help Tip + * @type {string} + * @category modules + */ +Adder.prototype.tooltipText = 'Adder ToolTip : Performs addition of numbers.'; +Adder.prototype.helplink = 'https://docs.circuitverse.org/#/miscellaneous?id=adder'; +Adder.prototype.objectType = 'Adder'; diff --git a/src/modules/AndGate.js b/src/modules/AndGate.js new file mode 100755 index 0000000..950b454 --- /dev/null +++ b/src/modules/AndGate.js @@ -0,0 +1,147 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * AndGate + * @extends CircuitElement + * @param {number} x - x coordinate of And Gate. + * @param {number} y - y coordinate of And Gate. + * @param {Scope=} scope - Cirucit on which and gate is drawn + * @param {string=} dir - direction of And Gate + * @param {number=} inputLength - number of input nodes + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class AndGate extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', inputLength = 2, bitWidth = 1) { + /** + * super call + */ + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['AndGate'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 20); + this.inp = []; + this.inputSize = inputLength; + + // variable inputLength , node creation + if (inputLength % 2 === 1) { + for (let i = 0; i < inputLength / 2 - 1; i++) { + const a = new Node(-10, -10 * (i + 1), 0, this); + this.inp.push(a); + } + let a = new Node(-10, 0, 0, this); + this.inp.push(a); + for (let i = inputLength / 2 + 1; i < inputLength; i++) { + a = new Node(-10, 10 * (i + 1 - inputLength / 2 - 1), 0, this); + this.inp.push(a); + } + } else { + for (let i = 0; i < inputLength / 2; i++) { + const a = new Node(-10, -10 * (i + 1), 0, this); + this.inp.push(a); + } + for (let i = inputLength / 2; i < inputLength; i++) { + const a = new Node(-10, 10 * (i + 1 - inputLength / 2), 0, this); + this.inp.push(a); + } + } + + this.output1 = new Node(20, 0, 1, this); + } + + /** + * @memberof AndGate + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.inputSize, this.bitWidth], + nodes: { + inp: this.inp.map(findNode), + output1: findNode(this.output1), + }, + + }; + return data; + } + + /** + * @memberof AndGate + * resolve output values based on inputData + */ + resolve() { + let result = this.inp[0].value || 0; + if (this.isResolvable() === false) { + return; + } + for (let i = 1; i < this.inputSize; i++) result &= ((this.inp[i].value) || 0); + this.output1.value = result; + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof AndGate + * function to draw And Gate + */ + customDraw() { + const ctx = simulationArea.context; + + ctx.beginPath(); + ctx.lineWidth = correctWidth(3); + ctx.strokeStyle = 'black'; // ("rgba(0,0,0,1)"); + ctx.fillStyle = 'white'; + const xx = this.x; + const yy = this.y; + + moveTo(ctx, -10, -20, xx, yy, this.direction); + lineTo(ctx, 0, -20, xx, yy, this.direction); + arc(ctx, 0, 0, 20, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); + lineTo(ctx, -10, 20, xx, yy, this.direction); + lineTo(ctx, -10, -20, xx, yy, this.direction); + ctx.closePath(); + + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + } +} + +/** + * @memberof AndGate + * Help Tip + * @type {string} + * @category modules + */ +AndGate.prototype.tooltipText = 'And Gate Tooltip : Implements logical conjunction'; + +/** + * @memberof AndGate + * @type {boolean} + * @category modules + */ +AndGate.prototype.alwaysResolve = true; + +/** + * @memberof AndGate + * @type {string} + * @category modules + */ +AndGate.prototype.verilogType = 'and'; + +/** + * @memberof AndGate + * function to change input nodes of the gate + * @category modules + */ +AndGate.prototype.changeInputSize = changeInputSize; +AndGate.prototype.helplink = 'https://docs.circuitverse.org/#/gates?id=and-gate'; +AndGate.prototype.objectType = 'AndGate'; diff --git a/src/modules/Arrow.js b/src/modules/Arrow.js new file mode 100755 index 0000000..d835064 --- /dev/null +++ b/src/modules/Arrow.js @@ -0,0 +1,78 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, +} from '../canvasApi'; + +/** + * @class + * Arrow + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @category modules + */ +export default class Arrow extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT') { + super(x, y, scope, dir, 8); + /* this is done in this.baseSetup() now + this.scope['Arrow'].push(this); + */ + this.rectangleObject = false; + this.fixedBitWidth = true; + this.setDimensions(30, 20); + } + + /** + * @memberof Arrow + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction], + }; + return data; + } + + /** + * @memberof Arrow + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + ctx.strokeStyle = 'red'; + ctx.fillStyle = 'white'; + + ctx.beginPath(); + + moveTo(ctx, -30, -3, xx, yy, this.direction); + lineTo(ctx, 10, -3, xx, yy, this.direction); + lineTo(ctx, 10, -15, xx, yy, this.direction); + lineTo(ctx, 30, 0, xx, yy, this.direction); + lineTo(ctx, 10, 15, xx, yy, this.direction); + lineTo(ctx, 10, 3, xx, yy, this.direction); + lineTo(ctx, -30, 3, xx, yy, this.direction); + ctx.closePath(); + ctx.stroke(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + } +} + +/** + * @memberof Arrow + * Help Tip + * @type {string} + * @category modules + */ +Arrow.prototype.tooltipText = 'Arrow ToolTip : Arrow Selected.'; +Arrow.prototype.propagationDelayFixed = true; +Arrow.prototype.helplink = 'https://docs.circuitverse.org/#/annotation?id=arrow'; +Arrow.prototype.objectType = 'Arrow'; diff --git a/src/modules/BitSelector.js b/src/modules/BitSelector.js new file mode 100755 index 0000000..02e1b42 --- /dev/null +++ b/src/modules/BitSelector.js @@ -0,0 +1,139 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode, extractBits } from '../node'; +import simulationArea from '../simulationArea'; +import { correctWidth, rect, fillText } from '../canvasApi'; +/** + * @class + * BitSelector + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @param {number=} selectorBitWidth - 1 by default + * @category modules + */ +export default class BitSelector extends CircuitElement { + constructor( + x, + y, + scope = globalScope, + dir = 'RIGHT', + bitWidth = 2, + selectorBitWidth = 1, + ) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['BitSelector'].push(this); + */ + this.setDimensions(20, 20); + this.selectorBitWidth = selectorBitWidth || parseInt(prompt('Enter Selector bitWidth'), 10); + this.rectangleObject = false; + this.inp1 = new Node(-20, 0, 0, this, this.bitWidth, 'Input'); + this.output1 = new Node(20, 0, 1, this, 1, 'Output'); + this.bitSelectorInp = new Node(0, 20, 0, this, this.selectorBitWidth, 'Bit Selector'); + } + + /** + * @memberof BitSelector + * Function to change selector Bitwidth + * @param {size} + */ + changeSelectorBitWidth(size) { + if (size === undefined || size < 1 || size > 32) return; + this.selectorBitWidth = size; + this.bitSelectorInp.bitWidth = size; + } + + /** + * @memberof BitSelector + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + + nodes: { + inp1: findNode(this.inp1), + output1: findNode(this.output1), + bitSelectorInp: findNode(this.bitSelectorInp), + }, + constructorParamaters: [this.direction, this.bitWidth, this.selectorBitWidth], + }; + return data; + } + + /** + * @memberof BitSelector + * function to change bitwidth of the element + * @param {number} bitWidth - new bitwidth + */ + newBitWidth(bitWidth) { + this.inp1.bitWidth = bitWidth; + this.bitWidth = bitWidth; + } + + /** + * @memberof BitSelector + * resolve output values based on inputData + */ + resolve() { + this.output1.value = extractBits(this.inp1.value, this.bitSelectorInp.value + 1, this.bitSelectorInp.value + 1); // (this.inp1.value^(1< this.state.length) this.state = '0'.repeat(bitWidth - this.state.length) + this.state; + else if (bitWidth < this.state.length) this.state = this.state.slice(this.bitWidth - bitWidth); + this.bitWidth = bitWidth; // ||parseInt(prompt("Enter bitWidth"),10); + this.output1.bitWidth = bitWidth; + this.setDimensions(10 * this.bitWidth, 10); + if (this.direction === 'RIGHT') { + this.output1.x = 10 * this.bitWidth; + this.output1.leftx = 10 * this.bitWidth; + } else if (this.direction === 'LEFT') { + this.output1.x = -10 * this.bitWidth; + this.output1.leftx = 10 * this.bitWidth; + } + } + + /** + * @memberof ConstantVal + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(1); + const xx = this.x; + const yy = this.y; + + rect2(ctx, -10 * this.bitWidth, -10, 20 * this.bitWidth, 20, xx, yy, 'RIGHT'); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + const bin = this.state; // dec2bin(this.state,this.bitWidth); + for (let k = 0; k < this.bitWidth; k++) { fillText(ctx, bin[k], xx - 10 * this.bitWidth + 10 + (k) * 20, yy + 5); } + ctx.fill(); + } + + /** + * @memberof ConstantVal + * function to change direction of ConstantVal + * @param {string} dir - new direction + */ + newDirection(dir) { + if (dir === this.direction) return; + this.direction = dir; + this.output1.refresh(); + if (dir === 'RIGHT' || dir === 'LEFT') { + this.output1.leftx = 10 * this.bitWidth; + this.output1.lefty = 0; + } else { + this.output1.leftx = 10; // 10*this.bitWidth; + this.output1.lefty = 0; + } + + this.output1.refresh(); + this.labelDirection = oppositeDirection[this.direction]; + } +} + +/** + * @memberof ConstantVal + * Help Tip + * @type {string} + * @category modules + */ +ConstantVal.prototype.tooltipText = 'Constant ToolTip: Bits are fixed. Double click element to change the bits.'; + +/** + * @memberof ConstantVal + * Help URL + * @type {string} + * @category modules + */ +ConstantVal.prototype.helplink = 'https://docs.circuitverse.org/#/inputElements?id=constantval'; + +/** + * @memberof ConstantVal + * @type {number} + * @category modules + */ +ConstantVal.prototype.propagationDelay = 0; +ConstantVal.prototype.objectType = 'ConstantVal'; diff --git a/src/modules/ControlledInverter.js b/src/modules/ControlledInverter.js new file mode 100755 index 0000000..0446229 --- /dev/null +++ b/src/modules/ControlledInverter.js @@ -0,0 +1,111 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, drawCircle2, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * ControlledInverter + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class ControlledInverter extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['ControlledInverter'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 15); + + this.inp1 = new Node(-10, 0, 0, this); + this.output1 = new Node(30, 0, 1, this); + this.state = new Node(0, 0, 0, this, 1, 'Enable'); + } + + /** + * @memberof ControlledInverter + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth], + nodes: { + output1: findNode(this.output1), + inp1: findNode(this.inp1), + state: findNode(this.state), + }, + }; + return data; + } + + /** + * @memberof ControlledInverter + * function to change bitwidth of the element + * @param {number} bitWidth - new bitwidth + */ + newBitWidth(bitWidth) { + this.inp1.bitWidth = bitWidth; + this.output1.bitWidth = bitWidth; + this.bitWidth = bitWidth; + } + + /** + * @memberof ControlledInverter + * resolve output values based on inputData + */ + resolve() { + if (this.isResolvable() === false) { + return; + } + if (this.state.value === 1) { + this.output1.value = ((~this.inp1.value >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); + simulationArea.simulationQueue.add(this.output1); + } + if (this.state.value === 0) { + this.output1.value = undefined; + } + } + + /** + * @memberof ControlledInverter + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(3); + + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + ctx.fillStyle = 'white'; + moveTo(ctx, -10, -15, xx, yy, this.direction); + lineTo(ctx, 20, 0, xx, yy, this.direction); + lineTo(ctx, -10, 15, xx, yy, this.direction); + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + drawCircle2(ctx, 25, 0, 5, xx, yy, this.direction); + ctx.stroke(); + } +} + +/** + * @memberof ControlledInverter + * Help Tip + * @type {string} + * @category modules + */ +ControlledInverter.prototype.tooltipText = 'Controlled Inverter ToolTip : Controlled buffer and NOT gate.'; +ControlledInverter.prototype.objectType = 'ControlledInverter'; diff --git a/src/modules/Counter.js b/src/modules/Counter.js new file mode 100644 index 0000000..d2e89c1 --- /dev/null +++ b/src/modules/Counter.js @@ -0,0 +1,122 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { lineTo, moveTo, fillText } from '../canvasApi'; + +/** + * @class + * Counter component. + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {number=} rows - number of rows + * @param {number=} cols - number of columns. + * Counts from zero to a particular maximum value, which is either + * specified by an input pin or determined by the Counter's bitWidth. + * The counter outputs its current value and a flag that indicates + * when the output value is zero and the clock is 1. + * The counter can be reset to zero at any point using the RESET pin. + * @category modules + */ +export default class Counter extends CircuitElement { + constructor(x, y, scope = globalScope, bitWidth = 8) { + super(x, y, scope, 'RIGHT', bitWidth); + /* this is done in this.baseSetup() now + this.scope['Counter'].push(this); + */ + this.directionFixed = true; + this.rectangleObject = true; + + this.setDimensions(20, 20); + + this.maxValue = new Node(-20, -10, 0, this, this.bitWidth, 'MaxValue'); + this.clock = new Node(-20, +10, 0, this, 1, 'Clock'); + this.reset = new Node(0, 20, 0, this, 1, 'Reset'); + this.output = new Node(20, -10, 1, this, this.bitWidth, 'Value'); + this.zero = new Node(20, 10, 1, this, 1, 'Zero'); + + this.value = 0; + this.prevClockState = undefined; + } + + customSave() { + return { + nodes: { + maxValue: findNode(this.maxValue), + clock: findNode(this.clock), + reset: findNode(this.reset), + output: findNode(this.output), + zero: findNode(this.zero), + }, + constructorParamaters: [this.bitWidth], + }; + } + + newBitWidth(bitWidth) { + this.bitWidth = bitWidth; + this.maxValue.bitWidth = bitWidth; + this.output.bitWidth = bitWidth; + } + + isResolvable() { + return true; + } + + resolve() { + // Max value is either the value in the input pin or the max allowed by the bitWidth. + const maxValue = this.maxValue.value != undefined ? this.maxValue.value : (1 << this.bitWidth) - 1; + let outputValue = this.value; + + // Increase value when clock is raised + if (this.clock.value != this.prevClockState && this.clock.value == 1) { + outputValue++; + } + this.prevClockState = this.clock.value; + + // Limit to the effective maximum value; this also accounts for bitWidth changes. + outputValue %= (maxValue + 1); + + // Reset to zero if RESET pin is on + if (this.reset.value == 1) { + outputValue = 0; + } + + // Output the new value + this.value = outputValue; + if (this.output.value != outputValue) { + this.output.value = outputValue; + simulationArea.simulationQueue.add(this.output); + } + + // Output the zero signal + const zeroValue = this.clock.value == 1 && outputValue == 0 ? 1 : 0; + if (this.zero.value != zeroValue) { + this.zero.value = zeroValue; + simulationArea.simulationQueue.add(this.zero); + } + } + + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + + ctx.beginPath(); + ctx.font = '20px Georgia'; + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + fillText(ctx, this.value.toString(16), this.x, this.y + 5); + ctx.fill(); + + ctx.beginPath(); + moveTo(ctx, -20, 5, xx, yy, this.direction); + lineTo(ctx, -15, 10, xx, yy, this.direction); + lineTo(ctx, -20, 15, xx, yy, this.direction); + ctx.stroke(); + } +} + +Counter.prototype.tooltipText = 'Counter: a binary counter from zero to a given maximum value'; +Counter.prototype.helplink = 'https://docs.circuitverse.org/#/inputElements?id=counter'; Counter.prototype.objectType = 'Counter'; +Counter.prototype.objectType = 'Counter'; diff --git a/src/modules/Decoder.js b/src/modules/Decoder.js new file mode 100755 index 0000000..3695924 --- /dev/null +++ b/src/modules/Decoder.js @@ -0,0 +1,158 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, rect, fillText, +} from '../canvasApi'; +/** + * @class + * Decoder + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class Decoder extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'LEFT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Decoder'].push(this); + */ + // this.controlSignalSize = controlSignalSize || parseInt(prompt("Enter control signal bitWidth"), 10); + this.outputsize = 1 << this.bitWidth; + this.xOff = 0; + this.yOff = 1; + if (this.bitWidth === 1) { + this.xOff = 10; + } + if (this.bitWidth <= 3) { + this.yOff = 2; + } + + // this.changeControlSignalSize = function(size) { + // if (size === undefined || size < 1 || size > 32) return; + // if (this.controlSignalSize === size) return; + // let obj = new window[this.objectType](this.x, this.y, this.scope, this.direction, this.bitWidth, size); + // this.cleanDelete(); + // simulationArea.lastSelected = obj; + // return obj; + // } + // this.mutableProperties = { + // "controlSignalSize": { + // name: "Control Signal Size", + // type: "number", + // max: "32", + // min: "1", + // func: "changeControlSignalSize", + // }, + // } + // eslint-disable-next-line no-shadow + this.newBitWidth = function (bitWidth) { + // this.bitWidth = bitWidth; + // for (let i = 0; i < this.inputSize; i++) { + // this.outputs1[i].bitWidth = bitWidth + // } + // this.input.bitWidth = bitWidth; + if (bitWidth === undefined || bitWidth < 1 || bitWidth > 32) return; + if (this.bitWidth === bitWidth) return; + const obj = new Decoder(this.x, this.y, this.scope, this.direction, bitWidth); + this.cleanDelete(); + simulationArea.lastSelected = obj; + return obj; + }; + + this.setDimensions(20 - this.xOff, this.yOff * 5 * (this.outputsize)); + this.rectangleObject = false; + this.input = new Node(20 - this.xOff, 0, 0, this); + + this.output1 = []; + for (let i = 0; i < this.outputsize; i++) { + const a = new Node(-20 + this.xOff, +this.yOff * 10 * (i - this.outputsize / 2) + 10, 1, this, 1); + this.output1.push(a); + } + + // this.controlSignalInput = new Node(0,this.yOff * 10 * (this.outputsize / 2 - 1) +this.xOff + 10, 0, this, this.controlSignalSize,"Control Signal"); + } + + /** + * @memberof Decoder + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth], + nodes: { + output1: this.output1.map(findNode), + input: findNode(this.input), + }, + }; + return data; + } + + /** + * @memberof Decoder + * resolve output values based on inputData + */ + resolve() { + for (let i = 0; i < this.output1.length; i++) { this.output1[i].value = 0; } + this.output1[this.input.value].value = 1; + for (let i = 0; i < this.output1.length; i++) { simulationArea.simulationQueue.add(this.output1[i]); } + } + + /** + * @memberof Decoder + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + + const xx = this.x; + const yy = this.y; + + // ctx.beginPath(); + // moveTo(ctx, 0,this.yOff * 10 * (this.outputsize / 2 - 1) + 10 + 0.5 *this.xOff, xx, yy, this.direction); + // lineTo(ctx, 0,this.yOff * 5 * (this.outputsize - 1) +this.xOff, xx, yy, this.direction); + // ctx.stroke(); + + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(4); + ctx.fillStyle = 'white'; + moveTo(ctx, -20 + this.xOff, -this.yOff * 10 * (this.outputsize / 2), xx, yy, this.direction); + lineTo(ctx, -20 + this.xOff, 20 + this.yOff * 10 * (this.outputsize / 2 - 1), xx, yy, this.direction); + lineTo(ctx, 20 - this.xOff, +this.yOff * 10 * (this.outputsize / 2 - 1) + this.xOff, xx, yy, this.direction); + lineTo(ctx, 20 - this.xOff, -this.yOff * 10 * (this.outputsize / 2) - this.xOff + 20, xx, yy, this.direction); + + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) { ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; } + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + // [xFill,yFill] = rotate(xx + this.output1[i].x - 7, yy + this.output1[i].y + 2); + // //console.log([xFill,yFill]) + for (let i = 0; i < this.outputsize; i++) { + if (this.direction === 'LEFT') fillText(ctx, String(i), xx + this.output1[i].x - 7, yy + this.output1[i].y + 2, 10); + else if (this.direction === 'RIGHT') fillText(ctx, String(i), xx + this.output1[i].x + 7, yy + this.output1[i].y + 2, 10); + else if (this.direction === 'UP') fillText(ctx, String(i), xx + this.output1[i].x, yy + this.output1[i].y - 5, 10); + else fillText(ctx, String(i), xx + this.output1[i].x, yy + this.output1[i].y + 10, 10); + } + ctx.fill(); + } +} + +/** + * @memberof Decoder + * Help Tip + * @type {string} + * @category modules + */ +Decoder.prototype.tooltipText = 'Decoder ToolTip : Converts coded inputs into coded outputs.'; +Decoder.prototype.helplink = 'https://docs.circuitverse.org/#/decodersandplexers?id=decoder'; +Decoder.prototype.objectType = 'Decoder'; diff --git a/src/modules/Demultiplexer.js b/src/modules/Demultiplexer.js new file mode 100755 index 0000000..732ee40 --- /dev/null +++ b/src/modules/Demultiplexer.js @@ -0,0 +1,163 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText, +} from '../canvasApi'; +/** + * @class + * Demultiplexer + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @param {number=} controlSignalSize - 1 by default + * @category modules + */ +export default class Demultiplexer extends CircuitElement { + constructor( + x, + y, + scope = globalScope, + dir = 'LEFT', + bitWidth = 1, + controlSignalSize = 1, + ) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Demultiplexer'].push(this); + */ + this.controlSignalSize = controlSignalSize || parseInt(prompt('Enter control signal bitWidth'), 10); + this.outputsize = 1 << this.controlSignalSize; + this.xOff = 0; + this.yOff = 1; + if (this.controlSignalSize === 1) { + this.xOff = 10; + } + if (this.controlSignalSize <= 3) { + this.yOff = 2; + } + + this.changeControlSignalSize = function (size) { + if (size === undefined || size < 1 || size > 32) return; + if (this.controlSignalSize === size) return; + const obj = new Demultiplexer(this.x, this.y, this.scope, this.direction, this.bitWidth, size); + this.cleanDelete(); + simulationArea.lastSelected = obj; + return obj; + }; + this.mutableProperties = { + controlSignalSize: { + name: 'Control Signal Size', + type: 'number', + max: '32', + min: '1', + func: 'changeControlSignalSize', + }, + }; + // eslint-disable-next-line no-shadow + this.newBitWidth = function (bitWidth) { + this.bitWidth = bitWidth; + for (let i = 0; i < this.outputsize; i++) { + this.output1[i].bitWidth = bitWidth; + } + this.input.bitWidth = bitWidth; + }; + + this.setDimensions(20 - this.xOff, this.yOff * 5 * (this.outputsize)); + this.rectangleObject = false; + this.input = new Node(20 - this.xOff, 0, 0, this); + + this.output1 = []; + for (let i = 0; i < this.outputsize; i++) { + const a = new Node(-20 + this.xOff, +this.yOff * 10 * (i - this.outputsize / 2) + 10, 1, this); + this.output1.push(a); + } + + this.controlSignalInput = new Node(0, this.yOff * 10 * (this.outputsize / 2 - 1) + this.xOff + 10, 0, this, this.controlSignalSize, 'Control Signal'); + } + + /** + * @memberof Demultiplexer + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth, this.controlSignalSize], + nodes: { + output1: this.output1.map(findNode), + input: findNode(this.input), + controlSignalInput: findNode(this.controlSignalInput), + }, + }; + return data; + } + + /** + * @memberof Demultiplexer + * resolve output values based on inputData + */ + resolve() { + for (let i = 0; i < this.output1.length; i++) { this.output1[i].value = 0; } + + this.output1[this.controlSignalInput.value].value = this.input.value; + + for (let i = 0; i < this.output1.length; i++) { simulationArea.simulationQueue.add(this.output1[i]); } + } + + /** + * @memberof Demultiplexer + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + + const xx = this.x; + const yy = this.y; + + ctx.beginPath(); + moveTo(ctx, 0, this.yOff * 10 * (this.outputsize / 2 - 1) + 10 + 0.5 * this.xOff, xx, yy, this.direction); + lineTo(ctx, 0, this.yOff * 5 * (this.outputsize - 1) + this.xOff, xx, yy, this.direction); + ctx.stroke(); + + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(4); + ctx.fillStyle = 'white'; + moveTo(ctx, -20 + this.xOff, -this.yOff * 10 * (this.outputsize / 2), xx, yy, this.direction); + lineTo(ctx, -20 + this.xOff, 20 + this.yOff * 10 * (this.outputsize / 2 - 1), xx, yy, this.direction); + lineTo(ctx, 20 - this.xOff, +this.yOff * 10 * (this.outputsize / 2 - 1) + this.xOff, xx, yy, this.direction); + lineTo(ctx, 20 - this.xOff, -this.yOff * 10 * (this.outputsize / 2) - this.xOff + 20, xx, yy, this.direction); + + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) { ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; } + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + // [xFill,yFill] = rotate(xx + this.output1[i].x - 7, yy + this.output1[i].y + 2); + // //console.log([xFill,yFill]) + for (let i = 0; i < this.outputsize; i++) { + if (this.direction === 'LEFT') fillText(ctx, String(i), xx + this.output1[i].x - 7, yy + this.output1[i].y + 2, 10); + else if (this.direction === 'RIGHT') fillText(ctx, String(i), xx + this.output1[i].x + 7, yy + this.output1[i].y + 2, 10); + else if (this.direction === 'UP') fillText(ctx, String(i), xx + this.output1[i].x, yy + this.output1[i].y - 5, 10); + else fillText(ctx, String(i), xx + this.output1[i].x, yy + this.output1[i].y + 10, 10); + } + ctx.fill(); + } +} + +/** + * @memberof Demultiplexer + * Help Tip + * @type {string} + * @category modules + */ +Demultiplexer.prototype.tooltipText = 'DeMultiplexer ToolTip : Multiple outputs and a single line input.'; +Demultiplexer.prototype.helplink = 'https://docs.circuitverse.org/#/decodersandplexers?id=demultiplexer'; +Demultiplexer.prototype.objectType = 'Demultiplexer'; diff --git a/src/modules/DigitalLed.js b/src/modules/DigitalLed.js new file mode 100755 index 0000000..fa6cc71 --- /dev/null +++ b/src/modules/DigitalLed.js @@ -0,0 +1,128 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * DigitalLed + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} color - color of led + * @category modules + */ +export default class DigitalLed extends CircuitElement { + constructor(x, y, scope = globalScope, color = 'Red') { + // Calling base class constructor + + super(x, y, scope, 'UP', 1); + /* this is done in this.baseSetup() now + this.scope['DigitalLed'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(10, 20); + this.inp1 = new Node(-40, 0, 0, this, 1); + this.directionFixed = true; + this.fixedBitWidth = true; + this.color = color; + const temp = colorToRGBA(this.color); + this.actualColor = `rgba(${temp[0]},${temp[1]},${temp[2]},${0.8})`; + } + + /** + * @memberof DigitalLed + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.color], + nodes: { + inp1: findNode(this.inp1), + }, + }; + return data; + } + + /** + * @memberof DigitalLed + * function to change color of the led + */ + changeColor(value) { + if (validColor(value)) { + this.color = value; + const temp = colorToRGBA(this.color); + this.actualColor = `rgba(${temp[0]},${temp[1]},${temp[2]},${0.8})`; + } + } + + /** + * @memberof DigitalLed + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + + const xx = this.x; + const yy = this.y; + + ctx.strokeStyle = '#e3e4e5'; + ctx.lineWidth = correctWidth(3); + ctx.beginPath(); + moveTo(ctx, -20, 0, xx, yy, this.direction); + lineTo(ctx, -40, 0, xx, yy, this.direction); + ctx.stroke(); + + ctx.strokeStyle = '#d3d4d5'; + ctx.fillStyle = ['rgba(227,228,229,0.8)', this.actualColor][this.inp1.value || 0]; + ctx.lineWidth = correctWidth(1); + + ctx.beginPath(); + + moveTo(ctx, -15, -9, xx, yy, this.direction); + lineTo(ctx, 0, -9, xx, yy, this.direction); + arc(ctx, 0, 0, 9, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); + lineTo(ctx, -15, 9, xx, yy, this.direction); + lineTo(ctx, -18, 12, xx, yy, this.direction); + arc(ctx, 0, 0, Math.sqrt(468), ((Math.PI / 2) + Math.acos(12 / Math.sqrt(468))), ((-Math.PI / 2) - Math.asin(18 / Math.sqrt(468))), xx, yy, this.direction); + lineTo(ctx, -15, -9, xx, yy, this.direction); + ctx.stroke(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + } +} + +/** + * @memberof DigitalLed + * Help Tip + * @type {string} + * @category modules + */ +DigitalLed.prototype.tooltipText = 'Digital Led ToolTip: Digital LED glows high when input is High(1).'; + +/** + * @memberof DigitalLed + * Help URL + * @type {string} + * @category modules + */ +DigitalLed.prototype.helplink = 'https://docs.circuitverse.org/#/outputs?id=digital-led'; + +/** + * @memberof DigitalLed + * Mutable properties of the element + * @type {JSON} + * @category modules + */ +DigitalLed.prototype.mutableProperties = { + color: { + name: 'Color: ', + type: 'text', + func: 'changeColor', + }, +}; +DigitalLed.prototype.objectType = 'DigitalLed'; diff --git a/src/modules/Flag.js b/src/modules/Flag.js new file mode 100755 index 0000000..d8e558d --- /dev/null +++ b/src/modules/Flag.js @@ -0,0 +1,187 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, rect2, fillText, +} from '../canvasApi'; +import plotArea from '../plotArea'; +/** + * @class + * Flag + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @param {string} identifier - id + * @category modules + */ +export default class Flag extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1, identifier) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Flag'].push(this); + */ + this.setWidth(60); + this.setHeight(20); + this.rectangleObject = false; + this.directionFixed = true; + this.orientationFixed = false; + this.identifier = identifier || (`F${this.scope.Flag.length}`); + this.plotValues = []; + + this.xSize = 10; + + this.inp1 = new Node(40, 0, 0, this); + } + + /** + * @memberof Flag + * funtion to Set plot values + * @type {string} + */ + setPlotValue() { + const time = plotArea.stopWatch.ElapsedMilliseconds; + + // //console.log("DEB:",time); + if (this.plotValues.length && this.plotValues[this.plotValues.length - 1][0] === time) { this.plotValues.pop(); } + + if (this.plotValues.length === 0) { + this.plotValues.push([time, this.inp1.value]); + return; + } + + if (this.plotValues[this.plotValues.length - 1][1] === this.inp1.value) { return; } + this.plotValues.push([time, this.inp1.value]); + } + + /** + * @memberof Flag + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth], + nodes: { + inp1: findNode(this.inp1), + }, + values: { + identifier: this.identifier, + }, + }; + return data; + } + + /** + * @memberof Flag + * set the flag id + * @param {number} id - identifier for flag + */ + setIdentifier(id = '') { + if (id.length === 0) return; + this.identifier = id; + const len = this.identifier.length; + if (len === 1) this.xSize = 20; + else if (len > 1 && len < 4) this.xSize = 10; + else this.xSize = 0; + } + + /** + * @memberof Flag + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = 'grey'; + ctx.fillStyle = '#fcfcfc'; + ctx.lineWidth = correctWidth(1); + const xx = this.x; + const yy = this.y; + + rect2(ctx, -50 + this.xSize, -20, 100 - 2 * this.xSize, 40, xx, yy, 'RIGHT'); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + + ctx.font = '14px Georgia'; + this.xOff = ctx.measureText(this.identifier).width; + + ctx.beginPath(); + rect2(ctx, -40 + this.xSize, -12, this.xOff + 10, 25, xx, yy, 'RIGHT'); + ctx.fillStyle = '#eee'; + ctx.strokeStyle = '#ccc'; + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.textAlign = 'center'; + ctx.fillStyle = 'black'; + fillText(ctx, this.identifier, xx - 35 + this.xOff / 2 + this.xSize, yy + 5, 14); + ctx.fill(); + + ctx.beginPath(); + ctx.font = '30px Georgia'; + ctx.textAlign = 'center'; + ctx.fillStyle = ['blue', 'red'][+(this.inp1.value === undefined)]; + if (this.inp1.value !== undefined) { fillText(ctx, this.inp1.value.toString(16), xx + 35 - this.xSize, yy + 8, 25); } else { fillText(ctx, 'x', xx + 35 - this.xSize, yy + 8, 25); } + ctx.fill(); + } + + /** + * @memberof Flag + * function to change direction of Flag + * @param {string} dir - new direction + */ + newDirection(dir) { + if (dir === this.direction) return; + this.direction = dir; + this.inp1.refresh(); + if (dir === 'RIGHT' || dir === 'LEFT') { + this.inp1.leftx = 50 - this.xSize; + } else if (dir === 'UP') { + this.inp1.leftx = 20; + } else { + this.inp1.leftx = 20; + } + // if(this.direction=="LEFT" || this.direction=="RIGHT") this.inp1.leftx=50-this.xSize; + // this.inp1.refresh(); + + this.inp1.refresh(); + } +} + +/** + * @memberof Flag + * Help Tip + * @type {string} + * @category modules + */ +Flag.prototype.tooltipText = 'FLag ToolTip: Use this for debugging and plotting.'; +Flag.prototype.helplink = 'https://docs.circuitverse.org/#/timing_diagrams?id=using-flags'; + +/** + * @memberof Flag + * Help URL + * @type {string} + * @category modules + */ +Flag.prototype.helplink = 'https://docs.circuitverse.org/#/miscellaneous?id=tunnel'; + +/** + * @memberof Flag + * Mutable properties of the element + * @type {JSON} + * @category modules + */ +Flag.prototype.mutableProperties = { + identifier: { + name: 'Debug Flag identifier', + type: 'text', + maxlength: '5', + func: 'setIdentifier', + }, +}; +Flag.prototype.objectType = 'Flag'; diff --git a/src/modules/Ground.js b/src/modules/Ground.js new file mode 100755 index 0000000..8598a69 --- /dev/null +++ b/src/modules/Ground.js @@ -0,0 +1,120 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * Ground + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class Ground extends CircuitElement { + constructor(x, y, scope = globalScope, bitWidth = 1) { + super(x, y, scope, 'RIGHT', bitWidth); + /* this is done in this.baseSetup() now + this.scope['Ground'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(10, 10); + this.directionFixed = true; + this.output1 = new Node(0, -10, 1, this); + } + + /** + * @memberof Ground + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + nodes: { + output1: findNode(this.output1), + }, + values: { + state: this.state, + }, + constructorParamaters: [this.direction, this.bitWidth], + }; + return data; + } + + /** + * @memberof Ground + * resolve output values based on inputData + */ + resolve() { + this.output1.value = 0; + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof Ground + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + nodes: { + output1: findNode(this.output1), + }, + constructorParamaters: [this.bitWidth], + }; + return data; + } + + /** + * @memberof Ground + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + + ctx.beginPath(); + ctx.strokeStyle = ['black', 'brown'][((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) + 0]; + ctx.lineWidth = correctWidth(3); + + const xx = this.x; + const yy = this.y; + + moveTo(ctx, 0, -10, xx, yy, this.direction); + lineTo(ctx, 0, 0, xx, yy, this.direction); + moveTo(ctx, -10, 0, xx, yy, this.direction); + lineTo(ctx, 10, 0, xx, yy, this.direction); + moveTo(ctx, -6, 5, xx, yy, this.direction); + lineTo(ctx, 6, 5, xx, yy, this.direction); + moveTo(ctx, -2.5, 10, xx, yy, this.direction); + lineTo(ctx, 2.5, 10, xx, yy, this.direction); + ctx.stroke(); + } +} + +/** + * @memberof Ground + * Help Tip + * @type {string} + * @category modules + */ +Ground.prototype.tooltipText = 'Ground: All bits are Low(0).'; + +/** + * @memberof Ground + * Help URL + * @type {string} + * @category modules + */ +Ground.prototype.helplink = 'https://docs.circuitverse.org/#/inputElements?id=ground'; + +/** + * @memberof Ground + * @type {number} + * @category modules + */ +Ground.prototype.propagationDelay = 0; +Ground.prototype.objectType = 'Ground'; diff --git a/src/modules/HexDisplay.js b/src/modules/HexDisplay.js new file mode 100755 index 0000000..658d130 --- /dev/null +++ b/src/modules/HexDisplay.js @@ -0,0 +1,155 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * HexDisplay + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @category modules + */ +export default class HexDisplay extends CircuitElement { + constructor(x, y, scope = globalScope) { + super(x, y, scope, 'RIGHT', 4); + /* this is done in this.baseSetup() now + this.scope['HexDisplay'].push(this); + */ + this.directionFixed = true; + this.fixedBitWidth = true; + this.setDimensions(30, 50); + this.inp = new Node(0, -50, 0, this, 4); + this.direction = 'RIGHT'; + } + + /** + * @memberof HexDisplay + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + + nodes: { + inp: findNode(this.inp), + }, + + }; + return data; + } + + /** + * @memberof HexDisplay + * function to draw element + */ + customDrawSegment(x1, y1, x2, y2, color) { + if (color === undefined) color = 'lightgrey'; + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = correctWidth(5); + const xx = this.x; + const yy = this.y; + + moveTo(ctx, x1, y1, xx, yy, this.direction); + lineTo(ctx, x2, y2, xx, yy, this.direction); + ctx.closePath(); + ctx.stroke(); + } + + /** + * @memberof HexDisplay + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + + const xx = this.x; + const yy = this.y; + + ctx.strokeStyle = 'black'; + ctx.lineWidth = correctWidth(3); + let a = 0; let b = 0; let c = 0; let d = 0; let e = 0; let f = 0; let + g = 0; + switch (this.inp.value) { + case 0: + a = b = c = d = e = f = 1; + break; + case 1: + b = c = 1; + break; + case 2: + a = b = g = e = d = 1; + break; + case 3: + a = b = g = c = d = 1; + break; + case 4: + f = g = b = c = 1; + break; + case 5: + a = f = g = c = d = 1; + break; + case 6: + a = f = g = e = c = d = 1; + break; + case 7: + a = b = c = 1; + break; + case 8: + a = b = c = d = e = g = f = 1; + break; + case 9: + a = f = g = b = c = 1; + break; + case 0xA: + a = f = b = c = g = e = 1; + break; + case 0xB: + f = e = g = c = d = 1; + break; + case 0xC: + a = f = e = d = 1; + break; + case 0xD: + b = c = g = e = d = 1; + break; + case 0xE: + a = f = g = e = d = 1; + break; + case 0xF: + a = f = g = e = 1; + break; + default: + } + this.customDrawSegment(18, -3, 18, -38, ['lightgrey', 'red'][b]); + this.customDrawSegment(18, 3, 18, 38, ['lightgrey', 'red'][c]); + this.customDrawSegment(-18, -3, -18, -38, ['lightgrey', 'red'][f]); + this.customDrawSegment(-18, 3, -18, 38, ['lightgrey', 'red'][e]); + this.customDrawSegment(-17, -38, 17, -38, ['lightgrey', 'red'][a]); + this.customDrawSegment(-17, 0, 17, 0, ['lightgrey', 'red'][g]); + this.customDrawSegment(-15, 38, 17, 38, ['lightgrey', 'red'][d]); + } +} + +/** + * @memberof HexDisplay + * Help Tip + * @type {string} + * @category modules + */ +HexDisplay.prototype.tooltipText = 'Hex Display ToolTip: Inputs a 4 Bit Hex number and displays it.'; + +/** + * @memberof HexDisplay + * Help URL + * @type {string} + * @category modules + */ +HexDisplay.prototype.helplink = 'https://docs.circuitverse.org/#/outputs?id=hex-display'; +HexDisplay.prototype.objectType = 'HexDisplay'; diff --git a/src/modules/Input.js b/src/modules/Input.js new file mode 100755 index 0000000..c3afab5 --- /dev/null +++ b/src/modules/Input.js @@ -0,0 +1,194 @@ +/* eslint-disable no-unused-expressions */ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, oppositeDirection, fillText, +} from '../canvasApi'; +import { getNextPosition } from '../modules'; +import { generateId } from '../utils'; +/** + * @class + * Input + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @param {Object=} layoutProperties - x,y and id + * @category modules + */ + +function bin2dec(binString) { + return parseInt(binString, 2); +} + +function dec2bin(dec, bitWidth = undefined) { + // only for positive nos + const bin = (dec).toString(2); + if (bitWidth == undefined) return bin; + return '0'.repeat(bitWidth - bin.length) + bin; +} + + +export default class Input extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1, layoutProperties) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Input'].push(this); + */ + if (layoutProperties) { this.layoutProperties = layoutProperties; } else { + this.layoutProperties = {}; + this.layoutProperties.x = 0; + this.layoutProperties.y = getNextPosition(0, scope); + this.layoutProperties.id = generateId(); + } + + // Call base class constructor + this.state = 0; + this.orientationFixed = false; + this.state = bin2dec(this.state); // in integer format + this.output1 = new Node(this.bitWidth * 10, 0, 1, this); + this.wasClicked = false; + this.directionFixed = true; + this.setWidth(this.bitWidth * 10); + this.rectangleObject = true; // Trying to make use of base class draw + } + + /** + * @memberof Input + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + nodes: { + output1: findNode(this.output1), + }, + values: { + state: this.state, + }, + constructorParamaters: [this.direction, this.bitWidth, this.layoutProperties], + }; + return data; + } + + /** + * @memberof Input + * resolve output values based on inputData + */ + resolve() { + this.output1.value = this.state; + simulationArea.simulationQueue.add(this.output1); + } + + // Check if override is necessary!! + + /** + * @memberof Input + * function to change bitwidth of the element + * @param {number} bitWidth - new bitwidth + */ + newBitWidth(bitWidth) { + if (bitWidth < 1) return; + const diffBitWidth = bitWidth - this.bitWidth; + this.bitWidth = bitWidth; // ||parseInt(prompt("Enter bitWidth"),10); + this.setWidth(this.bitWidth * 10); + this.state = 0; + this.output1.bitWidth = bitWidth; + if (this.direction === 'RIGHT') { + this.x -= 10 * diffBitWidth; + this.output1.x = 10 * this.bitWidth; + this.output1.leftx = 10 * this.bitWidth; + } else if (this.direction === 'LEFT') { + this.x += 10 * diffBitWidth; + this.output1.x = -10 * this.bitWidth; + this.output1.leftx = 10 * this.bitWidth; + } + } + + /** + * @memberof Input + * listener function to set selected index + */ + click() { // toggle + let pos = this.findPos(); + if (pos === 0) pos = 1; // minor correction + if (pos < 1 || pos > this.bitWidth) return; + this.state = ((this.state >>> 0) ^ (1 << (this.bitWidth - pos))) >>> 0; + } + + /** + * @memberof Input + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + + ctx.beginPath(); + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + const bin = dec2bin(this.state, this.bitWidth); + for (let k = 0; k < this.bitWidth; k++) { fillText(ctx, bin[k], xx - 10 * this.bitWidth + 10 + (k) * 20, yy + 5); } + ctx.fill(); + } + + /** + * @memberof Input + * function to change direction of input + * @param {string} dir - new direction + */ + newDirection(dir) { + if (dir === this.direction) return; + this.direction = dir; + this.output1.refresh(); + if (dir === 'RIGHT' || dir === 'LEFT') { + this.output1.leftx = 10 * this.bitWidth; + this.output1.lefty = 0; + } else { + this.output1.leftx = 10; // 10*this.bitWidth; + this.output1.lefty = 0; + } + + this.output1.refresh(); + this.labelDirection = oppositeDirection[this.direction]; + } + + /** + * @memberof Input + * function to find position of mouse click + */ + findPos() { + return Math.round((simulationArea.mouseX - this.x + 10 * this.bitWidth) / 20.0); + } +} + +/** + * @memberof Input + * Help Tip + * @type {string} + * @category modules + */ +Input.prototype.tooltipText = 'Input ToolTip: Toggle the individual bits by clicking on them.'; + +/** + * @memberof Input + * Help URL + * @type {string} + * @category modules + */ +Input.prototype.helplink = 'https://docs.circuitverse.org/#/inputElements?id=input'; + +/** + * @memberof Input + * @type {number} + * @category modules + */ +Input.prototype.propagationDelay = 0; +Input.prototype.objectType = 'Input'; diff --git a/src/modules/LSB.js b/src/modules/LSB.js new file mode 100755 index 0000000..7ca30d4 --- /dev/null +++ b/src/modules/LSB.js @@ -0,0 +1,132 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode, dec2bin } from '../node'; +import simulationArea from '../simulationArea'; +import { correctWidth, rect, fillText } from '../canvasApi'; +/** + * @class + * LSB + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class LSB extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['LSB'].push(this); + */ + this.leftDimensionX = 10; + this.rightDimensionX = 20; + this.setHeight(30); + this.directionFixed = true; + this.bitWidth = bitWidth || parseInt(prompt('Enter bitWidth'), 10); + this.rectangleObject = false; + this.inputSize = 1 << this.bitWidth; + + this.inp1 = new Node(-10, 0, 0, this, this.inputSize); + this.output1 = new Node(20, 0, 1, this, this.bitWidth); + this.enable = new Node(20, 20, 1, this, 1); + } + + /** + * @memberof LSB + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + + nodes: { + inp1: findNode(this.inp1), + output1: findNode(this.output1), + enable: findNode(this.enable), + }, + constructorParamaters: [this.direction, this.bitWidth], + }; + return data; + } + + /** + * @memberof LSB + * function to change bitwidth of the element + * @param {number} bitWidth - new bitwidth + */ + newBitWidth(bitWidth) { + // this.inputSize = 1 << bitWidth + this.inputSize = bitWidth; + this.inp1.bitWidth = this.inputSize; + this.bitWidth = bitWidth; + this.output1.bitWidth = bitWidth; + } + + /** + * @memberof LSB + * resolve output values based on inputData + */ + resolve() { + const inp = dec2bin(this.inp1.value); + let out = 0; + for (let i = inp.length - 1; i >= 0; i--) { + if (inp[i] === 1) { + out = inp.length - 1 - i; + break; + } + } + this.output1.value = out; + simulationArea.simulationQueue.add(this.output1); + if (inp !== 0) { + this.enable.value = 1; + } else { + this.enable.value = 0; + } + simulationArea.simulationQueue.add(this.enable); + } + + /** + * @memberof LSB + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + rect(ctx, xx - 10, yy - 30, 30, 60); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + fillText(ctx, 'LSB', xx + 6, yy - 12, 10); + fillText(ctx, 'EN', xx + this.enable.x - 12, yy + this.enable.y + 3, 8); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + if (this.output1.value !== undefined) { + fillText(ctx, this.output1.value, xx + 5, yy + 14, 13); + } + ctx.stroke(); + ctx.fill(); + } +} + +/** + * @memberof LSB + * Help Tip + * @type {string} + * @category modules + */ +LSB.prototype.tooltipText = 'LSB ToolTip : The least significant bit or the low-order bit.'; +LSB.prototype.helplink = 'https://docs.circuitverse.org/#/decodersandplexers?id=least-significant-bit-lsb-detector'; +LSB.prototype.objectType = 'LSB'; diff --git a/src/modules/MSB.js b/src/modules/MSB.js new file mode 100755 index 0000000..5e5c487 --- /dev/null +++ b/src/modules/MSB.js @@ -0,0 +1,126 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode, dec2bin } from '../node'; +import simulationArea from '../simulationArea'; +import { correctWidth, rect, fillText } from '../canvasApi'; +/** + * @class + * MSB + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class MSB extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['MSB'].push(this); + */ + // this.setDimensions(20, 20); + this.leftDimensionX = 10; + this.rightDimensionX = 20; + this.setHeight(30); + this.directionFixed = true; + this.bitWidth = bitWidth || parseInt(prompt('Enter bitWidth'), 10); + this.rectangleObject = false; + this.inputSize = 1 << this.bitWidth; + + this.inp1 = new Node(-10, 0, 0, this, this.inputSize); + this.output1 = new Node(20, 0, 1, this, this.bitWidth); + this.enable = new Node(20, 20, 1, this, 1); + } + + /** + * @memberof MSB + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + + nodes: { + inp1: findNode(this.inp1), + output1: findNode(this.output1), + enable: findNode(this.enable), + }, + constructorParamaters: [this.direction, this.bitWidth], + }; + return data; + } + + /** + * @memberof MSB + * function to change bitwidth of the element + * @param {number} bitWidth - new bitwidth + */ + newBitWidth(bitWidth) { + // this.inputSize = 1 << bitWidth + this.inputSize = bitWidth; + this.inp1.bitWidth = this.inputSize; + this.bitWidth = bitWidth; + this.output1.bitWidth = bitWidth; + } + + /** + * @memberof MSB + * resolve output values based on inputData + */ + resolve() { + const inp = this.inp1.value; + this.output1.value = (dec2bin(inp).length) - 1; + simulationArea.simulationQueue.add(this.output1); + if (inp !== 0) { + this.enable.value = 1; + } else { + this.enable.value = 0; + } + simulationArea.simulationQueue.add(this.enable); + } + + /** + * @memberof MSB + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + rect(ctx, xx - 10, yy - 30, 30, 60); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + fillText(ctx, 'MSB', xx + 6, yy - 12, 10); + fillText(ctx, 'EN', xx + this.enable.x - 12, yy + this.enable.y + 3, 8); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + if (this.output1.value !== undefined) { + fillText(ctx, this.output1.value, xx + 5, yy + 14, 13); + } + ctx.stroke(); + ctx.fill(); + } +} + +/** + * @memberof MSB + * Help Tip + * @type {string} + * @category modules + */ +MSB.prototype.tooltipText = 'MSB ToolTip : The most significant bit or the high-order bit.'; +MSB.prototype.helplink = 'https://docs.circuitverse.org/#/decodersandplexers?id=most-significant-bit-msb-detector'; +MSB.prototype.objectType = 'MSB'; diff --git a/src/modules/Multiplexer.js b/src/modules/Multiplexer.js new file mode 100755 index 0000000..ddc3d2b --- /dev/null +++ b/src/modules/Multiplexer.js @@ -0,0 +1,186 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * Multiplexer + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @param {number=} controlSignalSize - 1 by default + * @category modules + */ +export default class Multiplexer extends CircuitElement { + constructor( + x, + y, + scope = globalScope, + dir = 'RIGHT', + bitWidth = 1, + controlSignalSize = 1, + ) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Multiplexer'].push(this); + */ + this.controlSignalSize = controlSignalSize || parseInt(prompt('Enter control signal bitWidth'), 10); + this.inputSize = 1 << this.controlSignalSize; + this.xOff = 0; + this.yOff = 1; + if (this.controlSignalSize === 1) { + this.xOff = 10; + } + if (this.controlSignalSize <= 3) { + this.yOff = 2; + } + this.setDimensions(20 - this.xOff, this.yOff * 5 * (this.inputSize)); + this.rectangleObject = false; + this.inp = []; + for (let i = 0; i < this.inputSize; i++) { + const a = new Node(-20 + this.xOff, +this.yOff * 10 * (i - this.inputSize / 2) + 10, 0, this); + this.inp.push(a); + } + this.output1 = new Node(20 - this.xOff, 0, 1, this); + this.controlSignalInput = new Node(0, this.yOff * 10 * (this.inputSize / 2 - 1) + this.xOff + 10, 0, this, this.controlSignalSize, 'Control Signal'); + } + + /** + * @memberof Multiplexer + * function to change control signal of the element + */ + changeControlSignalSize(size) { + if (size === undefined || size < 1 || size > 32) return; + if (this.controlSignalSize === size) return; + const obj = new Multiplexer(this.x, this.y, this.scope, this.direction, this.bitWidth, size); + this.cleanDelete(); + simulationArea.lastSelected = obj; + return obj; + } + + /** + * @memberof Multiplexer + * function to change bitwidth of the element + * @param {number} bitWidth - bitwidth + */ + newBitWidth(bitWidth) { + this.bitWidth = bitWidth; + for (let i = 0; i < this.inputSize; i++) { + this.inp[i].bitWidth = bitWidth; + } + this.output1.bitWidth = bitWidth; + } + + /** + * @memberof Multiplexer + * @type {boolean} + */ + isResolvable() { + if (this.controlSignalInput.value !== undefined && this.inp[this.controlSignalInput.value].value !== undefined) return true; + return false; + } + + /** + * @memberof Multiplexer + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth, this.controlSignalSize], + nodes: { + inp: this.inp.map(findNode), + output1: findNode(this.output1), + controlSignalInput: findNode(this.controlSignalInput), + }, + }; + return data; + } + + /** + * @memberof Multiplexer + * resolve output values based on inputData + */ + resolve() { + if (this.isResolvable() === false) { + return; + } + this.output1.value = this.inp[this.controlSignalInput.value].value; + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof Multiplexer + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + + const xx = this.x; + const yy = this.y; + + ctx.beginPath(); + moveTo(ctx, 0, this.yOff * 10 * (this.inputSize / 2 - 1) + 10 + 0.5 * this.xOff, xx, yy, this.direction); + lineTo(ctx, 0, this.yOff * 5 * (this.inputSize - 1) + this.xOff, xx, yy, this.direction); + ctx.stroke(); + + ctx.lineWidth = correctWidth(3); + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + + ctx.fillStyle = 'white'; + moveTo(ctx, -20 + this.xOff, -this.yOff * 10 * (this.inputSize / 2), xx, yy, this.direction); + lineTo(ctx, -20 + this.xOff, 20 + this.yOff * 10 * (this.inputSize / 2 - 1), xx, yy, this.direction); + lineTo(ctx, 20 - this.xOff, +this.yOff * 10 * (this.inputSize / 2 - 1) + this.xOff, xx, yy, this.direction); + lineTo(ctx, 20 - this.xOff, -this.yOff * 10 * (this.inputSize / 2) - this.xOff + 20, xx, yy, this.direction); + + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) { ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; } + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + // ctx.lineWidth = correctWidth(2); + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + for (let i = 0; i < this.inputSize; i++) { + if (this.direction === 'RIGHT') fillText(ctx, String(i), xx + this.inp[i].x + 7, yy + this.inp[i].y + 2, 10); + else if (this.direction === 'LEFT') fillText(ctx, String(i), xx + this.inp[i].x - 7, yy + this.inp[i].y + 2, 10); + else if (this.direction === 'UP') fillText(ctx, String(i), xx + this.inp[i].x, yy + this.inp[i].y - 4, 10); + else fillText(ctx, String(i), xx + this.inp[i].x, yy + this.inp[i].y + 10, 10); + } + ctx.fill(); + } +} + +/** + * @memberof Multiplexer + * Help Tip + * @type {string} + * @category modules + */ +Multiplexer.prototype.tooltipText = 'Multiplexer ToolTip : Multiple inputs and a single line output.'; +Multiplexer.prototype.helplink = 'https://docs.circuitverse.org/#/decodersandplexers?id=multiplexer'; + +/** + * @memberof Multiplexer + * multable properties of element + * @type {JSON} + * @category modules + */ +Multiplexer.prototype.mutableProperties = { + controlSignalSize: { + name: 'Control Signal Size', + type: 'number', + max: '32', + min: '1', + func: 'changeControlSignalSize', + }, +}; +Multiplexer.prototype.objectType = 'Multiplexer'; diff --git a/src/modules/NandGate.js b/src/modules/NandGate.js new file mode 100755 index 0000000..6daccf9 --- /dev/null +++ b/src/modules/NandGate.js @@ -0,0 +1,144 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, drawCircle2, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * NandGate + * @extends CircuitElement + * @param {number} x - x coordinate of nand Gate. + * @param {number} y - y coordinate of nand Gate. + * @param {Scope=} scope - Cirucit on which nand gate is drawn + * @param {string=} dir - direction of nand Gate + * @param {number=} inputLength - number of input nodes + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class NandGate extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', inputLength = 2, bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['NandGate'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 20); + this.inp = []; + this.inputSize = inputLength; + // variable inputLength , node creation + if (inputLength % 2 === 1) { + for (let i = 0; i < inputLength / 2 - 1; i++) { + const a = new Node(-10, -10 * (i + 1), 0, this); + this.inp.push(a); + } + let a = new Node(-10, 0, 0, this); + this.inp.push(a); + for (let i = inputLength / 2 + 1; i < inputLength; i++) { + a = new Node(-10, 10 * (i + 1 - inputLength / 2 - 1), 0, this); + this.inp.push(a); + } + } else { + for (let i = 0; i < inputLength / 2; i++) { + const a = new Node(-10, -10 * (i + 1), 0, this); + this.inp.push(a); + } + for (let i = inputLength / 2; i < inputLength; i++) { + const a = new Node(-10, 10 * (i + 1 - inputLength / 2), 0, this); + this.inp.push(a); + } + } + this.output1 = new Node(30, 0, 1, this); + } + + /** + * @memberof NandGate + * fn to create save Json Data of object + * @return {JSON} + */ + // fn to create save Json Data of object + customSave() { + const data = { + + constructorParamaters: [this.direction, this.inputSize, this.bitWidth], + nodes: { + inp: this.inp.map(findNode), + output1: findNode(this.output1), + }, + }; + return data; + } + + /** + * @memberof NandGate + * resolve output values based on inputData + */ + resolve() { + let result = this.inp[0].value || 0; + if (this.isResolvable() === false) { + return; + } + for (let i = 1; i < this.inputSize; i++) result &= (this.inp[i].value || 0); + result = ((~result >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); + this.output1.value = result; + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof NandGate + * function to draw nand Gate + */ + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.lineWidth = correctWidth(3); + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; + const xx = this.x; + const yy = this.y; + moveTo(ctx, -10, -20, xx, yy, this.direction); + lineTo(ctx, 0, -20, xx, yy, this.direction); + arc(ctx, 0, 0, 20, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); + lineTo(ctx, -10, 20, xx, yy, this.direction); + lineTo(ctx, -10, -20, xx, yy, this.direction); + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.5)'; + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + drawCircle2(ctx, 25, 0, 5, xx, yy, this.direction); + ctx.stroke(); + } +} + +/** + * @memberof NandGate + * Help Tip + * @type {string} + * @category modules + */ +NandGate.prototype.tooltipText = 'Nand Gate ToolTip : Combination of AND and NOT gates'; + +/** + * @memberof NandGate + * @type {boolean} + * @category modules + */ +NandGate.prototype.alwaysResolve = true; + +/** + * @memberof NandGate + * function to change input nodes of the gate + * @category modules + */ +NandGate.prototype.changeInputSize = changeInputSize; + +/** + * @memberof NandGate + * @type {string} + * @category modules + */ +NandGate.prototype.verilogType = 'nand'; +NandGate.prototype.helplink = 'https://docs.circuitverse.org/#/gates?id=nand-gate'; +NandGate.prototype.objectType = 'NandGate'; diff --git a/src/modules/NorGate.js b/src/modules/NorGate.js new file mode 100755 index 0000000..da74cdb --- /dev/null +++ b/src/modules/NorGate.js @@ -0,0 +1,143 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, bezierCurveTo, moveTo, arc2, drawCircle2, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * NorGate + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} inputs - number of input nodes + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class NorGate extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', inputs = 2, bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['NorGate'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 20); + + this.inp = []; + this.inputSize = inputs; + + if (inputs % 2 === 1) { + for (let i = 0; i < inputs / 2 - 1; i++) { + const a = new Node(-10, -10 * (i + 1), 0, this); + this.inp.push(a); + } + let a = new Node(-10, 0, 0, this); + this.inp.push(a); + for (let i = inputs / 2 + 1; i < inputs; i++) { + a = new Node(-10, 10 * (i + 1 - inputs / 2 - 1), 0, this); + this.inp.push(a); + } + } else { + for (let i = 0; i < inputs / 2; i++) { + const a = new Node(-10, -10 * (i + 1), 0, this); + this.inp.push(a); + } + for (let i = inputs / 2; i < inputs; i++) { + const a = new Node(-10, 10 * (i + 1 - inputs / 2), 0, this); + this.inp.push(a); + } + } + this.output1 = new Node(30, 0, 1, this); + } + + /** + * @memberof NorGate + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.inputSize, this.bitWidth], + nodes: { + inp: this.inp.map(findNode), + output1: findNode(this.output1), + }, + }; + return data; + } + + /** + * @memberof NorGate + * resolve output values based on inputData + */ + resolve() { + let result = this.inp[0].value || 0; + for (let i = 1; i < this.inputSize; i++) result |= (this.inp[i].value || 0); + result = ((~result >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); + this.output1.value = result; + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof NorGate + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(3); + + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + ctx.fillStyle = 'white'; + + moveTo(ctx, -10, -20, xx, yy, this.direction, true); + bezierCurveTo(0, -20, +15, -10, 20, 0, xx, yy, this.direction); + bezierCurveTo(0 + 15, 0 + 10, 0, 0 + 20, -10, +20, xx, yy, this.direction); + bezierCurveTo(0, 0, 0, 0, -10, -20, xx, yy, this.direction); + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.5)'; + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + drawCircle2(ctx, 25, 0, 5, xx, yy, this.direction); + ctx.stroke(); + // for debugging + } +} + +/** + * @memberof NorGate + * Help Tip + * @type {string} + * @category modules + */ +NorGate.prototype.tooltipText = 'Nor Gate ToolTip : Combination of OR gate and NOT gate.'; + +/** + * @memberof NorGate + * @type {boolean} + * @category modules + */ +NorGate.prototype.alwaysResolve = true; + + +/** + * @memberof SevenSegDisplay + * function to change input nodes of the element + * @category modules + */ +NorGate.prototype.changeInputSize = changeInputSize; + +/** + * @memberof SevenSegDisplay + * @type {string} + * @category modules + */ +NorGate.prototype.verilogType = 'nor'; +NorGate.prototype.helplink = 'https://docs.circuitverse.org/#/gates?id=nor-gate'; +NorGate.prototype.objectType = 'NorGate'; diff --git a/src/modules/NotGate.js b/src/modules/NotGate.js new file mode 100755 index 0000000..6067ff1 --- /dev/null +++ b/src/modules/NotGate.js @@ -0,0 +1,94 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, drawCircle2, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * NotGate + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class NotGate extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['NotGate'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 15); + + this.inp1 = new Node(-10, 0, 0, this); + this.output1 = new Node(20, 0, 1, this); + } + + /** + * @memberof NotGate + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth], + nodes: { + output1: findNode(this.output1), + inp1: findNode(this.inp1), + }, + }; + return data; + } + + /** + * @memberof NotGate + * resolve output values based on inputData + */ + resolve() { + if (this.isResolvable() === false) { + return; + } + this.output1.value = ((~this.inp1.value >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof NotGate + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = 'black'; + ctx.lineWidth = correctWidth(3); + + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + ctx.fillStyle = 'white'; + moveTo(ctx, -10, -10, xx, yy, this.direction); + lineTo(ctx, 10, 0, xx, yy, this.direction); + lineTo(ctx, -10, 10, xx, yy, this.direction); + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + drawCircle2(ctx, 15, 0, 5, xx, yy, this.direction); + ctx.stroke(); + } +} + +/** + * @memberof NotGate + * Help Tip + * @type {string} + * @category modules + */ +NotGate.prototype.tooltipText = 'Not Gate Tooltip : Inverts the input digital signal.'; +NotGate.prototype.helplink = 'https://docs.circuitverse.org/#/gates?id=not-gate'; +NotGate.prototype.objectType = 'NotGate'; diff --git a/src/modules/OrGate.js b/src/modules/OrGate.js new file mode 100755 index 0000000..532a507 --- /dev/null +++ b/src/modules/OrGate.js @@ -0,0 +1,142 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, bezierCurveTo, moveTo, arc2, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * OrGate + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} inputs - number of input nodes + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class OrGate extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', inputs = 2, bitWidth = 1) { + // Calling base class constructor + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['OrGate'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 20); + // Inherit base class prototype + this.inp = []; + this.inputSize = inputs; + if (inputs % 2 === 1) { + for (let i = Math.floor(inputs / 2) - 1; i >= 0; i--) { + const a = new Node(-10, -10 * (i + 1), 0, this); + this.inp.push(a); + } + let a = new Node(-10, 0, 0, this); + this.inp.push(a); + for (let i = 0; i < Math.floor(inputs / 2); i++) { + a = new Node(-10, 10 * (i + 1), 0, this); + this.inp.push(a); + } + } else { + for (let i = inputs / 2 - 1; i >= 0; i--) { + const a = new Node(-10, -10 * (i + 1), 0, this); + this.inp.push(a); + } + for (let i = 0; i < inputs / 2; i++) { + const a = new Node(-10, 10 * (i + 1), 0, this); + this.inp.push(a); + } + } + this.output1 = new Node(20, 0, 1, this); + } + + /** + * @memberof OrGate + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + + constructorParamaters: [this.direction, this.inputSize, this.bitWidth], + + nodes: { + inp: this.inp.map(findNode), + output1: findNode(this.output1), + }, + }; + return data; + } + + /** + * @memberof OrGate + * resolve output values based on inputData + */ + resolve() { + let result = this.inp[0].value || 0; + if (this.isResolvable() === false) { + return; + } + for (let i = 1; i < this.inputSize; i++) result |= (this.inp[i].value || 0); + this.output1.value = result; + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof OrGate + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(3); + + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + ctx.fillStyle = 'white'; + + moveTo(ctx, -10, -20, xx, yy, this.direction, true); + bezierCurveTo(0, -20, +15, -10, 20, 0, xx, yy, this.direction); + bezierCurveTo(0 + 15, 0 + 10, 0, 0 + 20, -10, +20, xx, yy, this.direction); + bezierCurveTo(0, 0, 0, 0, -10, -20, xx, yy, this.direction); + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + } +} + +/** + * @memberof OrGate + * Help Tip + * @type {string} + * @category modules + */ +OrGate.prototype.tooltipText = 'Or Gate Tooltip : Implements logical disjunction'; + +/** + * @memberof OrGate + * function to change input nodes of the element + * @category modules + */ +OrGate.prototype.changeInputSize = changeInputSize; + +/** + * @memberof SevenSegDisplay + * @type {boolean} + * @category modules + */ +OrGate.prototype.alwaysResolve = true; + +/** + * @memberof SevenSegDisplay + * @type {string} + * @category modules + */ +OrGate.prototype.verilogType = 'or'; +OrGate.prototype.helplink = 'https://docs.circuitverse.org/#/gates?id=or-gate'; +OrGate.prototype.objectType = 'OrGate'; diff --git a/src/modules/Output.js b/src/modules/Output.js new file mode 100755 index 0000000..45858c3 --- /dev/null +++ b/src/modules/Output.js @@ -0,0 +1,179 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, fillText, rect2, oppositeDirection, +} from '../canvasApi'; +import { getNextPosition } from '../modules'; +import { generateId } from '../utils'; + + +function bin2dec(binString) { + return parseInt(binString, 2); +} + +function dec2bin(dec, bitWidth = undefined) { + // only for positive nos + const bin = (dec).toString(2); + if (bitWidth == undefined) return bin; + return '0'.repeat(bitWidth - bin.length) + bin; +} + + +/** + * @class + * Output + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} inputLength - number of input nodes + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class Output extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'LEFT', bitWidth = 1, layoutProperties) { + // Calling base class constructor + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Output'].push(this); + */ + if (layoutProperties) { this.layoutProperties = layoutProperties; } else { + this.layoutProperties = {}; + this.layoutProperties.x = scope.layout.width; + this.layoutProperties.y = getNextPosition(scope.layout.width, scope); + this.layoutProperties.id = generateId(); + } + + this.rectangleObject = false; + this.directionFixed = true; + this.orientationFixed = false; + this.setDimensions(this.bitWidth * 10, 10); + this.inp1 = new Node(this.bitWidth * 10, 0, 0, this); + } + + /** + * @memberof Output + * function to generate verilog for output + * @return {string} + */ + generateVerilog() { + return `assign ${this.label} = ${this.inp1.verilogLabel};`; + } + + /** + * @memberof Output + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + nodes: { + inp1: findNode(this.inp1), + }, + constructorParamaters: [this.direction, this.bitWidth, this.layoutProperties], + }; + return data; + } + + /** + * @memberof Output + * function to change bitwidth of the element + * @param {number} bitWidth - new bitwidth + */ + newBitWidth(bitWidth) { + if (bitWidth < 1) return; + const diffBitWidth = bitWidth - this.bitWidth; + this.state = undefined; + this.inp1.bitWidth = bitWidth; + this.bitWidth = bitWidth; + this.setWidth(10 * this.bitWidth); + + if (this.direction === 'RIGHT') { + this.x -= 10 * diffBitWidth; + this.inp1.x = 10 * this.bitWidth; + this.inp1.leftx = 10 * this.bitWidth; + } else if (this.direction === 'LEFT') { + this.x += 10 * diffBitWidth; + this.inp1.x = -10 * this.bitWidth; + this.inp1.leftx = 10 * this.bitWidth; + } + } + + /** + * @memberof Output + * function to draw element + */ + customDraw() { + this.state = this.inp1.value; + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ['blue', 'red'][+(this.inp1.value === undefined)]; + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + + rect2(ctx, -10 * this.bitWidth, -10, 20 * this.bitWidth, 20, xx, yy, 'RIGHT'); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) { ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; } + + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.font = '20px Georgia'; + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + let bin; + if (this.state === undefined) { bin = 'x'.repeat(this.bitWidth); } else { bin = dec2bin(this.state, this.bitWidth); } + + for (let k = 0; k < this.bitWidth; k++) { fillText(ctx, bin[k], xx - 10 * this.bitWidth + 10 + (k) * 20, yy + 5); } + ctx.fill(); + } + + /** + * @memberof Output + * function to change direction of Output + * @param {string} dir - new direction + */ + newDirection(dir) { + if (dir === this.direction) return; + this.direction = dir; + this.inp1.refresh(); + if (dir === 'RIGHT' || dir === 'LEFT') { + this.inp1.leftx = 10 * this.bitWidth; + this.inp1.lefty = 0; + } else { + this.inp1.leftx = 10; // 10*this.bitWidth; + this.inp1.lefty = 0; + } + + this.inp1.refresh(); + this.labelDirection = oppositeDirection[this.direction]; + } +} + +/** + * @memberof Output + * Help Tip + * @type {string} + * @category modules + */ +Output.prototype.tooltipText = 'Output ToolTip: Simple output element showing output in binary.'; + +/** + * @memberof Output + * Help URL + * @type {string} + * @category modules + */ +Output.prototype.helplink = 'https://docs.circuitverse.org/#/outputs?id=output'; + +/** + * @memberof Output + * @type {number} + * @category modules + */ +Output.prototype.propagationDelay = 0; +Output.prototype.objectType = 'Output'; diff --git a/src/modules/Power.js b/src/modules/Power.js new file mode 100755 index 0000000..8bcf119 --- /dev/null +++ b/src/modules/Power.js @@ -0,0 +1,127 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * Power + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class Power extends CircuitElement { + constructor(x, y, scope = globalScope, bitWidth = 1) { + super(x, y, scope, 'RIGHT', bitWidth); + /* this is done in this.baseSetup() now + this.scope['Power'].push(this); + */ + this.directionFixed = true; + this.rectangleObject = false; + this.setDimensions(10, 10); + this.output1 = new Node(0, 10, 1, this); + } + + /** + * @memberof Power + * resolve output values based on inputData + */ + resolve() { + this.output1.value = ~0 >>> (32 - this.bitWidth); + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof Power + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + + nodes: { + output1: findNode(this.output1), + }, + constructorParamaters: [this.bitWidth], + }; + return data; + } + + /** + * @memberof Power + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(3); + ctx.fillStyle = 'green'; + moveTo(ctx, 0, -10, xx, yy, this.direction); + lineTo(ctx, -10, 0, xx, yy, this.direction); + lineTo(ctx, 10, 0, xx, yy, this.direction); + lineTo(ctx, 0, -10, xx, yy, this.direction); + ctx.closePath(); + ctx.stroke(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + moveTo(ctx, 0, 0, xx, yy, this.direction); + lineTo(ctx, 0, 10, xx, yy, this.direction); + ctx.stroke(); + } +} + +/** + * @memberof Power + * Help Tip + * @type {string} + * @category modules + */ +Power.prototype.tooltipText = 'Power: All bits are High(1).'; + +/** + * @memberof Power + * Help URL + * @type {string} + * @category modules + */ +Power.prototype.helplink = 'https://docs.circuitverse.org/#/inputElements?id=power'; + +/** + * @memberof Power + * @type {number} + * @category modules + */ +Power.prototype.propagationDelay = 0; + +function getNextPosition(x = 0, scope = globalScope) { + let possibleY = 20; + const done = {}; + for (let i = 0; i < scope.Input.length; i++) { + if (scope.Input[i].layoutProperties.x === x) { done[scope.Input[i].layoutProperties.y] = 1; } + } + for (let i = 0; i < scope.Output.length; i++) { + if (scope.Output[i].layoutProperties.x === x) { done[scope.Output[i].layoutProperties.y] = 1; } + } + while (done[possibleY] || done[possibleY + 10] || done[possibleY - 10]) { possibleY += 10; } + const height = possibleY + 20; + if (height > scope.layout.height) { + const oldHeight = scope.layout.height; + scope.layout.height = height; + for (let i = 0; i < scope.Input.length; i++) { + if (scope.Input[i].layoutProperties.y === oldHeight) { scope.Input[i].layoutProperties.y = scope.layout.height; } + } + for (let i = 0; i < scope.Output.length; i++) { + if (scope.Output[i].layoutProperties.y === oldHeight) { scope.Output[i].layoutProperties.y = scope.layout.height; } + } + } + return possibleY; +} +Power.prototype.objectType = 'Power'; diff --git a/src/modules/PriorityEncoder.js b/src/modules/PriorityEncoder.js new file mode 100755 index 0000000..b7912ce --- /dev/null +++ b/src/modules/PriorityEncoder.js @@ -0,0 +1,165 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode, dec2bin } from '../node'; +import simulationArea from '../simulationArea'; +import { correctWidth, rect, fillText } from '../canvasApi'; +/** + * @class + * PriorityEncoder + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class PriorityEncoder extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['PriorityEncoder'].push(this); + */ + this.bitWidth = bitWidth || parseInt(prompt('Enter bitWidth'), 10); + this.inputSize = 1 << this.bitWidth; + + this.yOff = 1; + if (this.bitWidth <= 3) { + this.yOff = 2; + } + + this.setDimensions(20, this.yOff * 5 * (this.inputSize)); + this.directionFixed = true; + this.rectangleObject = false; + + this.inp1 = []; + for (let i = 0; i < this.inputSize; i++) { + const a = new Node(-10, +this.yOff * 10 * (i - this.inputSize / 2) + 10, 0, this, 1); + this.inp1.push(a); + } + + this.output1 = []; + for (let i = 0; i < this.bitWidth; i++) { + const a = new Node(30, +2 * 10 * (i - this.bitWidth / 2) + 10, 1, this, 1); + this.output1.push(a); + } + + this.enable = new Node(10, 20 + this.inp1[this.inputSize - 1].y, 1, this, 1); + } + + /** + * @memberof PriorityEncoder + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + + nodes: { + inp1: this.inp1.map(findNode), + output1: this.output1.map(findNode), + enable: findNode(this.enable), + }, + constructorParamaters: [this.direction, this.bitWidth], + }; + return data; + } + + /** + * @memberof PriorityEncoder + * function to change bitwidth of the element + * @param {number} bitWidth - new bitwidth + */ + newBitWidth(bitWidth) { + if (bitWidth === undefined || bitWidth < 1 || bitWidth > 32) return; + if (this.bitWidth === bitWidth) return; + + this.bitWidth = bitWidth; + const obj = new PriorityEncoder(this.x, this.y, this.scope, this.direction, this.bitWidth); + this.inputSize = 1 << bitWidth; + + this.cleanDelete(); + simulationArea.lastSelected = obj; + return obj; + } + + /** + * @memberof PriorityEncoder + * resolve output values based on inputData + */ + resolve() { + let out = 0; + let temp = 0; + for (let i = this.inputSize - 1; i >= 0; i--) { + if (this.inp1[i].value === 1) { + out = dec2bin(i); + break; + } + } + temp = out; + + if (out.length !== undefined) { + this.enable.value = 1; + } else { + this.enable.value = 0; + } + simulationArea.simulationQueue.add(this.enable); + + if (temp.length === undefined) { + temp = '0'; + for (let i = 0; i < this.bitWidth - 1; i++) { + temp = `0${temp}`; + } + } + + if (temp.length !== this.bitWidth) { + for (let i = temp.length; i < this.bitWidth; i++) { + temp = `0${temp}`; + } + } + + for (let i = this.bitWidth - 1; i >= 0; i--) { + this.output1[this.bitWidth - 1 - i].value = Number(temp[i]); + simulationArea.simulationQueue.add(this.output1[this.bitWidth - 1 - i]); + } + } + + /** + * @memberof PriorityEncoder + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + if (this.bitWidth <= 3) { rect(ctx, xx - 10, yy - 10 - this.yOff * 5 * (this.inputSize), 40, 20 * (this.inputSize + 1)); } else { rect(ctx, xx - 10, yy - 10 - this.yOff * 5 * (this.inputSize), 40, 10 * (this.inputSize + 3)); } + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + for (let i = 0; i < this.inputSize; i++) { + fillText(ctx, String(i), xx, yy + this.inp1[i].y + 2, 10); + } + for (let i = 0; i < this.bitWidth; i++) { + fillText(ctx, String(i), xx + this.output1[0].x - 10, yy + this.output1[i].y + 2, 10); + } + fillText(ctx, 'EN', xx + this.enable.x, yy + this.enable.y - 5, 10); + ctx.fill(); + } +} + +/** + * @memberof PriorityEncoder + * Help Tip + * @type {string} + * @category modules + */ +PriorityEncoder.prototype.tooltipText = 'Priority Encoder ToolTip : Compresses binary inputs into a smaller number of outputs.'; +PriorityEncoder.prototype.helplink = 'https://docs.circuitverse.org/#/decodersandplexers?id=priority-encoder'; +PriorityEncoder.prototype.objectType = 'PriorityEncoder'; diff --git a/src/modules/RGBLed.js b/src/modules/RGBLed.js new file mode 100755 index 0000000..e989dd9 --- /dev/null +++ b/src/modules/RGBLed.js @@ -0,0 +1,122 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * RGBLed + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @category modules + */ +export default class RGBLed extends CircuitElement { + constructor(x, y, scope = globalScope) { + // Calling base class constructor + super(x, y, scope, 'UP', 8); + /* this is done in this.baseSetup() now + this.scope['RGBLed'].push(this); + */ + this.rectangleObject = false; + this.inp = []; + this.setDimensions(10, 10); + this.inp1 = new Node(-40, -10, 0, this, 8); + this.inp2 = new Node(-40, 0, 0, this, 8); + this.inp3 = new Node(-40, 10, 0, this, 8); + this.inp.push(this.inp1); + this.inp.push(this.inp2); + this.inp.push(this.inp3); + this.directionFixed = true; + this.fixedBitWidth = true; + } + + /** + * @memberof RGBLed + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + nodes: { + inp1: findNode(this.inp1), + inp2: findNode(this.inp2), + inp3: findNode(this.inp3), + }, + }; + return data; + } + + /** + * @memberof RGBLed + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + + const xx = this.x; + const yy = this.y; + + ctx.strokeStyle = 'green'; + ctx.lineWidth = correctWidth(3); + ctx.beginPath(); + moveTo(ctx, -20, 0, xx, yy, this.direction); + lineTo(ctx, -40, 0, xx, yy, this.direction); + ctx.stroke(); + + ctx.strokeStyle = 'red'; + ctx.lineWidth = correctWidth(3); + ctx.beginPath(); + moveTo(ctx, -20, -10, xx, yy, this.direction); + lineTo(ctx, -40, -10, xx, yy, this.direction); + ctx.stroke(); + + ctx.strokeStyle = 'blue'; + ctx.lineWidth = correctWidth(3); + ctx.beginPath(); + moveTo(ctx, -20, 10, xx, yy, this.direction); + lineTo(ctx, -40, 10, xx, yy, this.direction); + ctx.stroke(); + + const a = this.inp1.value; + const b = this.inp2.value; + const c = this.inp3.value; + ctx.strokeStyle = '#d3d4d5'; + ctx.fillStyle = [`rgba(${a}, ${b}, ${c}, 0.8)`, 'rgba(227, 228, 229, 0.8)'][((a === undefined || b === undefined || c === undefined)) + 0]; + // ctx.fillStyle = ["rgba(200, 200, 200, 0.3)","rgba(227, 228, 229, 0.8)"][((a === undefined || b === undefined || c === undefined) || (a === 0 && b === 0 && c === 0)) + 0]; + ctx.lineWidth = correctWidth(1); + + ctx.beginPath(); + + moveTo(ctx, -18, -11, xx, yy, this.direction); + lineTo(ctx, 0, -11, xx, yy, this.direction); + arc(ctx, 0, 0, 11, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); + lineTo(ctx, -18, 11, xx, yy, this.direction); + lineTo(ctx, -21, 15, xx, yy, this.direction); + arc(ctx, 0, 0, Math.sqrt(666), ((Math.PI / 2) + Math.acos(15 / Math.sqrt(666))), ((-Math.PI / 2) - Math.asin(21 / Math.sqrt(666))), xx, yy, this.direction); + lineTo(ctx, -18, -11, xx, yy, this.direction); + ctx.stroke(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + } +} + +/** + * @memberof RGBLed + * Help Tip + * @type {string} + * @category modules + */ +RGBLed.prototype.tooltipText = 'RGB Led ToolTip: RGB Led inputs 8 bit values for the colors RED, GREEN and BLUE.'; + +/** + * @memberof RGBLed + * Help URL + * @type {string} + * @category modules + */ +RGBLed.prototype.helplink = 'https://docs.circuitverse.org/#/outputs?id=rgb-led'; +RGBLed.prototype.objectType = 'RGBLed'; diff --git a/src/modules/RGBLedMatrix.js b/src/modules/RGBLedMatrix.js new file mode 100644 index 0000000..d69b12e --- /dev/null +++ b/src/modules/RGBLedMatrix.js @@ -0,0 +1,330 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, rect2, rotate, lineTo, moveTo, +} from '../canvasApi'; + +/** + * @class + * RGBLedMatrix + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {number=} rows - number of rows + * @param {number=} cols - number of columns. + * @category modules + */ +export default class RGBLedMatrix extends CircuitElement { + constructor( + x, + y, + scope = globalScope, + { + rows = 8, + columns = 8, + ledSize = 2, + showGrid = true, + colors = [], + } = {}, + ) { + super(x, y, scope, 'RIGHT', 8); + /* this is done in this.baseSetup() now + this.scope['RGBLedMatrix'].push(this); + */ + this.fixedBitWidth = true; + this.directionFixed = true; + this.rectangleObject = true; + this.alwaysResolve = true; + this.labelDirection = 'UP'; + this.leftDimensionX = 0; + this.upDimensionY = 0; + + // These pins provide bulk-editing of the colors + this.rowEnableNodes = []; // 1-bit pin for each row, on the left side. + this.columnEnableNodes = []; // 1-bit pin for each column, on the bottom. + this.columnColorNodes = []; // 24-bit pin for each column, on the top. + + // These pins provide single-pixel editing; these are on the right side. + this.colorNode = new Node(0, -10, NODE_INPUT, this, 24, 'COLOR'); + this.rowNode = new Node(0, 0, NODE_INPUT, this, 1, 'ROW'); + this.columnNode = new Node(0, 10, NODE_INPUT, this, 1, 'COLUMN'); + + this.colors = colors; + this.showGrid = showGrid; + this.changeSize(rows, columns, ledSize, false); + } + + toggleGrid() { + this.showGrid = !this.showGrid; + } + + changeRows(rows) { + this.changeSize(rows, this.columns, this.ledSize, true); + } + + changeColumns(columns) { + this.changeSize(this.rows, columns, this.ledSize, true); + } + + changeLedSize(ledSize) { + this.changeSize(this.rows, this.columns, ledSize, true); + } + + changeSize(rows, columns, ledSize, move) { + rows = parseInt(rows, 10); + if (isNaN(rows) || rows < 0 || rows > this.maxRows) return; + + columns = parseInt(columns, 10); + if (isNaN(columns) || columns < 0 || columns > this.maxColumns) return; + + ledSize = parseInt(ledSize, 10); + if (isNaN(ledSize) || ledSize < 0 || ledSize > this.maxLedSize) return; + + // The size of an individual LED, in canvas units. + const ledWidth = 10 * ledSize; + const ledHeight = 10 * ledSize; + + // The size of the LED matrix, in canvas units. + const gridWidth = ledWidth * columns; + const gridHeight = ledHeight * rows; + + // We need to position the element in the 10x10 grid. + // Depending on the size of the leds we need to add different paddings so position correctly. + const padding = ledSize % 2 ? 5 : 10; + + // The dimensions of the element, in canvas units. + const halfWidth = gridWidth / 2 + padding; + const halfHeight = gridHeight / 2 + padding; + + // Move the element in order to keep the position of the nodes stable so wires don't break. + if (move) { + this.x -= this.leftDimensionX - halfWidth; + this.y -= this.upDimensionY - halfHeight; + } + + // Update the dimensions of the element. + this.setDimensions(halfWidth, halfHeight); + + // Offset of the nodes in relation to the element's center. + const nodePadding = [10, 20, 20][ledSize - 1]; + const nodeOffsetX = nodePadding - halfWidth; + const nodeOffsetY = nodePadding - halfHeight; + + // When the led size changes it is better to delete all nodes to break connected the wires. + // Otherwise, wires can end up connected in unexpected ways. + const resetAllNodes = ledSize != this.ledSize; + + // Delete unused row-enable nodes, reposition remaining nodes and add new nodes. + this.rowEnableNodes.splice(resetAllNodes ? 0 : rows).forEach((node) => node.delete()); + this.rowEnableNodes.forEach((node, i) => { + node.x = node.leftx = -halfWidth; + node.y = node.lefty = i * ledHeight + nodeOffsetY; + }); + while (this.rowEnableNodes.length < rows) { + this.rowEnableNodes.push(new Node(-halfWidth, this.rowEnableNodes.length * ledHeight + nodeOffsetY, NODE_INPUT, this, 1, `R${this.rowEnableNodes.length}`)); + } + + // Delete unused column-enable nodes, reposition remaining nodes and add new nodes. + this.columnEnableNodes.splice(resetAllNodes ? 0 : columns).forEach((node) => node.delete()); + this.columnEnableNodes.forEach((node, i) => { + node.x = node.leftx = i * ledWidth + nodeOffsetX; + node.y = node.lefty = halfHeight; + }); + while (this.columnEnableNodes.length < columns) { + this.columnEnableNodes.push(new Node(this.columnEnableNodes.length * ledWidth + nodeOffsetX, halfHeight, NODE_INPUT, this, 1, `C${this.columnEnableNodes.length}`)); + } + + // Delete unused column color nodes, reposition remaining nodes and add new nodes. + this.columnColorNodes.splice(resetAllNodes ? 0 : columns).forEach((node) => node.delete()); + this.columnColorNodes.forEach((node, i) => { + node.x = node.leftx = i * ledWidth + nodeOffsetX; + node.y = node.lefty = -halfHeight; + }); + while (this.columnColorNodes.length < columns) { + this.columnColorNodes.push(new Node(this.columnColorNodes.length * ledWidth + nodeOffsetX, -halfHeight, NODE_INPUT, this, 24, `CLR${this.columnColorNodes.length}`)); + } + + // Delete unused color storage and add storage for new rows. + this.colors.splice(rows); + this.colors.forEach((c) => c.splice(columns)); + while (this.colors.length < rows) { + this.colors.push([]); + } + + // Reposition the single-pixel nodes + this.rowNode.bitWidth = Math.ceil(Math.log2(rows)); + this.rowNode.label = `ROW (${this.rowNode.bitWidth} bits)`; + this.columnNode.bitWidth = Math.ceil(Math.log2(columns)); + this.columnNode.label = `COLUMN (${this.columnNode.bitWidth} bits)`; + const singlePixelNodePadding = rows > 1 ? nodeOffsetY : nodeOffsetY - 10; + const singlePixelNodeDistance = (rows <= 2) ? 10 : ledHeight; + [this.colorNode, this.rowNode, this.columnNode].forEach((node, i) => { + node.x = node.leftx = halfWidth; + node.y = node.lefty = i * singlePixelNodeDistance + singlePixelNodePadding; + }); + + // Store the new values + this.rows = rows; + this.columns = columns; + this.ledSize = ledSize; + + return this; + } + + customSave() { + // Save the size of the LED matrix. + // Unlike a read LED matrix, we also persist the color of each pixel. + // This allows circuit preview to show the colors at the time the simulation was saved. + return { + constructorParamaters: [{ + rows: this.rows, + columns: this.columns, + ledSize: this.ledSize, + showGrid: this.showGrid, + colors: this.colors, + }], + nodes: { + rowEnableNodes: this.rowEnableNodes.map(findNode), + columnEnableNodes: this.columnEnableNodes.map(findNode), + columnColorNodes: this.columnColorNodes.map(findNode), + colorNode: findNode(this.colorNode), + rowNode: findNode(this.rowNode), + columnNode: findNode(this.columnNode), + }, + }; + } + + resolve() { + const colorValue = this.colorNode.value; + const hasColorValue = colorValue != undefined; + + const { rows } = this; + const { columns } = this; + const { rowEnableNodes } = this; + const { columnEnableNodes } = this; + const { columnColorNodes } = this; + const { colors } = this; + + for (let row = 0; row < rows; row++) { + if (rowEnableNodes[row].value === 1) { + for (let column = 0; column < columns; column++) { + // Method 1: set pixel by rowEnable + columnColor pins + const columnColor = columnColorNodes[column].value; + if (columnColor !== undefined) { + colors[row][column] = columnColor; + } + + // Method 2: set pixel by rowEnable + columnEnable + color pins + if (hasColorValue && columnEnableNodes[column].value === 1) { + colors[row][column] = colorValue; + } + } + } + } + + // Method 3: set pixel by write + pixel index + color pins. + const hasRowNodeValue = this.rowNode.value != undefined || rows == 1; + const hasColumnNodeValue = this.columnNode.value != undefined || columns == 1; + if (hasColorValue && hasRowNodeValue && hasColumnNodeValue) { + const rowNodeValue = this.rowNode.value || 0; + const columnNodeValue = this.columnNode.value || 0; + if (rowNodeValue < rows && columnNodeValue < columns) { + colors[rowNodeValue][columnNodeValue] = colorValue; + } + } + } + + customDraw() { + const ctx = simulationArea.context; + const { rows } = this; + const { columns } = this; + const { colors } = this; + const xx = this.x; + const yy = this.y; + const dir = this.direction; + const ledWidth = 10 * this.ledSize; + const ledHeight = 10 * this.ledSize; + const top = this.rowEnableNodes[0].y - ledHeight / 2; + const left = this.columnColorNodes[0].x - ledWidth / 2; + const width = this.columns * ledWidth; + const height = this.rows * ledHeight; + const bottom = top + height; + const right = left + width; + + const [w, h] = rotate(ledWidth * globalScope.scale, ledHeight * globalScope.scale, dir); + const xoffset = Math.round(globalScope.ox + xx * globalScope.scale); + const yoffset = Math.round(globalScope.oy + yy * globalScope.scale); + for (let row = 0; row < rows; row++) { + for (let column = 0; column < columns; column++) { + const color = colors[row][column] || 0; + ctx.beginPath(); + ctx.fillStyle = `rgb(${(color & 0xFF0000) >> 16},${(color & 0xFF00) >> 8},${color & 0xFF})`; + let x1; let + y1; + [x1, y1] = rotate(left + column * ledWidth, top + row * ledHeight, dir); + x1 *= globalScope.scale; + y1 *= globalScope.scale; + ctx.rect(xoffset + x1, yoffset + y1, w, h); + ctx.fill(); + } + } + + if (this.showGrid) { + ctx.beginPath(); + ctx.strokeStyle = '#323232'; + ctx.lineWidth = correctWidth(1); + rect2(ctx, left, top, width, height, xx, yy, dir); + for (let x = left + ledWidth; x < right; x += ledWidth) { + moveTo(ctx, x, top, xx, yy, dir); + lineTo(ctx, x, bottom, xx, yy, dir); + } + for (let y = top + ledHeight; y < bottom; y += ledHeight) { + moveTo(ctx, left, y, xx, yy, dir); + lineTo(ctx, right, y, xx, yy, dir); + } + ctx.stroke(); + } + } +} + +RGBLedMatrix.prototype.tooltipText = 'RGB Led Matrix'; + +// Limit the size of the matrix otherwise the simulation starts to lag. +RGBLedMatrix.prototype.maxRows = 128; +RGBLedMatrix.prototype.maxColumns = 128; + +// Let the user choose between 3 sizes of LEDs: small, medium and large. +RGBLedMatrix.prototype.maxLedSize = 3; + +RGBLedMatrix.prototype.mutableProperties = { + rows: { + name: 'Rows', + type: 'number', + max: RGBLedMatrix.prototype.maxRows, + min: 1, + func: 'changeRows', + }, + columns: { + name: 'Columns', + type: 'number', + max: RGBLedMatrix.prototype.maxColumns, + min: 1, + func: 'changeColumns', + }, + ledSize: { + name: 'LED Size', + type: 'number', + max: RGBLedMatrix.prototype.maxLedSize, + min: 1, + func: 'changeLedSize', + }, + showGrid: { + name: 'Toggle Grid', + type: 'button', + max: RGBLedMatrix.prototype.maxLedSize, + min: 1, + func: 'toggleGrid', + }, +}; RGBLedMatrix.prototype.objectType = 'RGBLedMatrix'; diff --git a/src/modules/Random.js b/src/modules/Random.js new file mode 100644 index 0000000..31e253d --- /dev/null +++ b/src/modules/Random.js @@ -0,0 +1,107 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { fillText, lineTo, moveTo } from '../canvasApi'; +/** + * @class + * Random + * Random is used to generate random value. + * It has 2 input node: + * clock and max random output value + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * @category modules + */ +export default class Random extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Random'].push(this); + */ + this.directionFixed = true; + this.setDimensions(20, 20); + this.rectangleObject = true; + this.currentRandomNo = 0; + this.clockInp = new Node(-20, +10, 0, this, 1, 'Clock'); + this.maxValue = new Node(-20, -10, 0, this, this.bitWidth, 'MaxValue'); + this.output = new Node(20, -10, 1, this, this.bitWidth, 'RandomValue'); + this.prevClockState = 0; + this.wasClicked = false; + } + + /** + * @memberof Random + * return true if clock is connected and if maxValue is set or unconnected. + */ + isResolvable() { + if (this.clockInp.value != undefined && (this.maxValue.value != undefined || this.maxValue.connections.length == 0)) { return true; } + return false; + } + + newBitWidth(bitWidth) { + this.bitWidth = bitWidth; + this.maxValue.bitWidth = bitWidth; + this.output.bitWidth = bitWidth; + } + + /** + * @memberof Random + * Edge triggered when the clock state changes a + * Random number is generated less then the maxValue. + */ + resolve() { + // console.log("HIT") + const maxValue = this.maxValue.connections.length ? this.maxValue.value + 1 : (2 << (this.bitWidth - 1)); + if (this.clockInp.value != undefined) { + if (this.clockInp.value != this.prevClockState) { + if (this.clockInp.value == 1) { + this.currentRandomNo = Math.floor(Math.random() * maxValue); + } + this.prevClockState = this.clockInp.value; + } + } + if (this.output.value != this.currentRandomNo) { + this.output.value = this.currentRandomNo; + simulationArea.simulationQueue.add(this.output); + } + } + + customSave() { + const data = { + nodes: { + clockInp: findNode(this.clockInp), + maxValue: findNode(this.maxValue), + output: findNode(this.output), + }, + constructorParamaters: [this.direction, this.bitWidth], + + }; + return data; + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + const xx = this.x; + const yy = this.y; + ctx.font = '20px Georgia'; + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + fillText(ctx, this.currentRandomNo.toString(10), this.x, this.y + 5); + ctx.fill(); + ctx.beginPath(); + moveTo(ctx, -20, 5, xx, yy, this.direction); + lineTo(ctx, -15, 10, xx, yy, this.direction); + lineTo(ctx, -20, 15, xx, yy, this.direction); + ctx.stroke(); + } +} + +Random.prototype.tooltipText = 'Random ToolTip : Random Selected.'; + +Random.prototype.helplink = 'https://docs.circuitverse.org/#/inputElements?id=random'; + +Random.prototype.objectType = 'Random'; diff --git a/src/modules/Rectangle.js b/src/modules/Rectangle.js new file mode 100755 index 0000000..ec191ff --- /dev/null +++ b/src/modules/Rectangle.js @@ -0,0 +1,146 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { correctWidth, rect } from '../canvasApi'; +/** + * @class + * Rectangle + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {number=} rows - number of rows + * @param {number=} cols - number of columns. + * @category modules + */ +export default class Rectangle extends CircuitElement { + constructor(x, y, scope = globalScope, rows = 15, cols = 20) { + super(x, y, scope, 'RIGHT', 1); + /* this is done in this.baseSetup() now + this.scope['Rectangle'].push(this); + */ + this.directionFixed = true; + this.fixedBitWidth = true; + this.rectangleObject = false; + this.cols = cols || parseInt(prompt('Enter cols:'), 10); + this.rows = rows || parseInt(prompt('Enter rows:'), 10); + this.setSize(); + } + + /** + * @memberof Rectangle + * @param {number} size - new size of rows + */ + changeRowSize(size) { + if (size === undefined || size < 5 || size > 1000) return; + if (this.rows === size) return; + this.rows = parseInt(size, 10); + this.setSize(); + return this; + } + + /** + * @memberof Rectangle + * @param {number} size - new size of columns + */ + changeColSize(size) { + if (size === undefined || size < 5 || size > 1000) return; + if (this.cols === size) return; + this.cols = parseInt(size, 10); + this.setSize(); + return this; + } + + /** + * @memberof Rectangle + * listener function to change direction of rectangle + * @param {string} dir - new direction + */ + keyDown3(dir) { + if (dir === 'ArrowRight') { this.changeColSize(this.cols + 2); } + if (dir === 'ArrowLeft') { this.changeColSize(this.cols - 2); } + if (dir === 'ArrowDown') { this.changeRowSize(this.rows + 2); } + if (dir === 'ArrowUp') { this.changeRowSize(this.rows - 2); } + } + + /** + * @memberof Rectangle + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.rows, this.cols], + }; + return data; + } + + /** + * @memberof Rectangle + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = 'rgba(0,0,0,1)'; + ctx.setLineDash([5 * globalScope.scale, 5 * globalScope.scale]); + ctx.lineWidth = correctWidth(1.5); + const xx = this.x; + const yy = this.y; + rect(ctx, xx, yy, this.elementWidth, this.elementHeight); + ctx.stroke(); + + if (simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) { + ctx.fillStyle = 'rgba(255, 255, 32,0.1)'; + ctx.fill(); + } + ctx.setLineDash([]); + } + + /** + * @memberof Rectangle + * function to reset or (internally) set size + */ + setSize() { + this.elementWidth = this.cols * 10; + this.elementHeight = this.rows * 10; + this.upDimensionY = 0; + this.leftDimensionX = 0; + this.rightDimensionX = this.elementWidth; + this.downDimensionY = this.elementHeight; + } +} + +/** + * @memberof Rectangle + * Help Tip + * @type {string} + * @category modules + */ +Rectangle.prototype.tooltipText = 'Rectangle ToolTip : Used to Box the Circuit or area you want to highlight.'; +Rectangle.prototype.helplink = 'https://docs.circuitverse.org/#/annotation?id=rectangle'; +Rectangle.prototype.propagationDelayFixed = true; + +/** + * @memberof Rectangle + * Mutable properties of the element + * @type {JSON} + * @category modules + */ +Rectangle.prototype.mutableProperties = { + cols: { + name: 'Columns', + type: 'number', + max: '1000', + min: '5', + func: 'changeColSize', + }, + rows: { + name: 'Rows', + type: 'number', + max: '1000', + min: '5', + func: 'changeRowSize', + }, +}; +Rectangle.prototype.objectType = 'Rectangle'; diff --git a/src/modules/Rom.js b/src/modules/Rom.js new file mode 100755 index 0000000..485511d --- /dev/null +++ b/src/modules/Rom.js @@ -0,0 +1,202 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * Rom + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {Array=} data - bit width per node. + * @category modules + */ +export default class Rom extends CircuitElement { + constructor( + x, + y, + scope = globalScope, + data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ) { + super(x, y, scope, 'RIGHT', 1); + /* this is done in this.baseSetup() now + this.scope['Rom'].push(this); + */ + this.fixedBitWidth = true; + this.directionFixed = true; + this.rectangleObject = false; + this.setDimensions(80, 50); + this.memAddr = new Node(-80, 0, 0, this, 4, 'Address'); + this.en = new Node(0, 50, 0, this, 1, 'Enable'); + this.dataOut = new Node(80, 0, 1, this, 8, 'DataOut'); + this.data = data || prompt('Enter data').split(' ').map((lambda) => parseInt(lambda, 16)); + // console.log(this.data); + } + + /** + * @memberof Rom + * Checks if the element is resolvable + * @return {boolean} + */ + isResolvable() { + if ((this.en.value === 1 || this.en.connections.length === 0) && this.memAddr.value !== undefined) return true; + return false; + } + + /** + * @memberof Rom + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.data], + nodes: { + memAddr: findNode(this.memAddr), + dataOut: findNode(this.dataOut), + en: findNode(this.en), + }, + + }; + return data; + } + + /** + * @memberof Rom + * function to find position of the index of part of rom selected. + * @return {number} + */ + findPos() { + const i = Math.floor((simulationArea.mouseX - this.x + 35) / 20); + const j = Math.floor((simulationArea.mouseY - this.y + 35) / 16); + if (i < 0 || j < 0 || i > 3 || j > 3) return undefined; + return j * 4 + i; + } + + /** + * @memberof Rom + * listener function to set selected index + * @return {number} + */ + click() { // toggle + this.selectedIndex = this.findPos(); + } + + /** + * @memberof Rom + * to take input in rom + * @return {number} + */ + keyDown(key) { + if (key === 'Backspace') this.delete(); + if (this.selectedIndex === undefined) return; + key = key.toLowerCase(); + if (!~'1234567890abcdef'.indexOf(key)) return; + + this.data[this.selectedIndex] = (this.data[this.selectedIndex] * 16 + parseInt(key, 16)) % 256; + } + + /** + * @memberof Rom + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + const hoverIndex = this.findPos(); + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + ctx.beginPath(); + rect2(ctx, -this.leftDimensionX, -this.upDimensionY, this.leftDimensionX + this.rightDimensionX, this.upDimensionY + this.downDimensionY, this.x, this.y, [this.direction, 'RIGHT'][+this.directionFixed]); + if (hoverIndex === undefined && ((!simulationArea.shiftDown && this.hover) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this))) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + ctx.strokeStyle = 'black'; + ctx.fillStyle = '#fafafa'; + ctx.lineWidth = correctWidth(1); + ctx.beginPath(); + for (let i = 0; i < 16; i += 4) { + for (let j = i; j < i + 4; j++) { + rect2(ctx, (j % 4) * 20, i * 4, 20, 16, xx - 35, yy - 35); + } + } + ctx.fill(); + ctx.stroke(); + if (hoverIndex !== undefined) { + ctx.beginPath(); + ctx.fillStyle = 'yellow'; + rect2(ctx, (hoverIndex % 4) * 20, Math.floor(hoverIndex / 4) * 16, 20, 16, xx - 35, yy - 35); + ctx.fill(); + ctx.stroke(); + } + if (this.selectedIndex !== undefined) { + ctx.beginPath(); + ctx.fillStyle = 'lightgreen'; + rect2(ctx, (this.selectedIndex % 4) * 20, Math.floor(this.selectedIndex / 4) * 16, 20, 16, xx - 35, yy - 35); + ctx.fill(); + ctx.stroke(); + } + if (this.memAddr.value !== undefined) { + ctx.beginPath(); + ctx.fillStyle = 'green'; + rect2(ctx, (this.memAddr.value % 4) * 20, Math.floor(this.memAddr.value / 4) * 16, 20, 16, xx - 35, yy - 35); + ctx.fill(); + ctx.stroke(); + } + + ctx.beginPath(); + ctx.fillStyle = 'Black'; + fillText3(ctx, 'A', -65, 5, xx, yy, fontSize = 16, font = 'Georgia', textAlign = 'right'); + fillText3(ctx, 'D', 75, 5, xx, yy, fontSize = 16, font = 'Georgia', textAlign = 'right'); + fillText3(ctx, 'En', 5, 47, xx, yy, fontSize = 16, font = 'Georgia', textAlign = 'right'); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = 'Black'; + for (let i = 0; i < 16; i += 4) { + for (let j = i; j < i + 4; j++) { + let s = this.data[j].toString(16); + if (s.length < 2) s = `0${s}`; + fillText3(ctx, s, (j % 4) * 20, i * 4, xx - 35 + 10, yy - 35 + 12, fontSize = 14, font = 'Georgia', textAlign = 'center'); + } + } + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = 'Black'; + for (let i = 0; i < 16; i += 4) { + let s = i.toString(16); + if (s.length < 2) s = `0${s}`; + fillText3(ctx, s, 0, i * 4, xx - 40, yy - 35 + 12, fontSize = 14, font = 'Georgia', textAlign = 'right'); + } + ctx.fill(); + } + + /** + * @memberof Rom + * resolve output values based on inputData + */ + resolve() { + if (this.isResolvable() === false) { + return; + } + this.dataOut.value = this.data[this.memAddr.value]; + simulationArea.simulationQueue.add(this.dataOut); + } +} + +/** + * @memberof Rom + * Help Tip + * @type {string} + * @category modules + */ +Rom.prototype.tooltipText = 'Read-only memory'; +Rom.prototype.helplink = 'https://docs.circuitverse.org/#/memoryElements?id=rom'; +Rom.prototype.objectType = 'Rom'; diff --git a/src/modules/SevenSegDisplay.js b/src/modules/SevenSegDisplay.js new file mode 100755 index 0000000..b5f00b2 --- /dev/null +++ b/src/modules/SevenSegDisplay.js @@ -0,0 +1,116 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, rect, +} from '../canvasApi'; + +/** + * @class + * SevenSegDisplay + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @category modules + */ +export default class SevenSegDisplay extends CircuitElement { + constructor(x, y, scope = globalScope) { + super(x, y, scope, 'RIGHT', 1); + /* this is done in this.baseSetup() now + this.scope['SevenSegDisplay'].push(this); + */ + this.fixedBitWidth = true; + this.directionFixed = true; + this.setDimensions(30, 50); + + this.g = new Node(-20, -50, 0, this); + this.f = new Node(-10, -50, 0, this); + this.a = new Node(+10, -50, 0, this); + this.b = new Node(+20, -50, 0, this); + this.e = new Node(-20, +50, 0, this); + this.d = new Node(-10, +50, 0, this); + this.c = new Node(+10, +50, 0, this); + this.dot = new Node(+20, +50, 0, this); + this.direction = 'RIGHT'; + } + + /** + * @memberof SevenSegDisplay + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + + nodes: { + g: findNode(this.g), + f: findNode(this.f), + a: findNode(this.a), + b: findNode(this.b), + d: findNode(this.d), + e: findNode(this.e), + c: findNode(this.c), + dot: findNode(this.dot), + }, + }; + return data; + } + + /** + * @memberof SevenSegDisplay + * helper function to create save Json Data of object + */ + customDrawSegment(x1, y1, x2, y2, color) { + if (color === undefined) color = 'lightgrey'; + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = correctWidth(5); + const xx = this.x; + const yy = this.y; + moveTo(ctx, x1, y1, xx, yy, this.direction); + lineTo(ctx, x2, y2, xx, yy, this.direction); + ctx.closePath(); + ctx.stroke(); + } + + /** + * @memberof SevenSegDisplay + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + this.customDrawSegment(18, -3, 18, -38, ['lightgrey', 'red'][this.b.value]); + this.customDrawSegment(18, 3, 18, 38, ['lightgrey', 'red'][this.c.value]); + this.customDrawSegment(-18, -3, -18, -38, ['lightgrey', 'red'][this.f.value]); + this.customDrawSegment(-18, 3, -18, 38, ['lightgrey', 'red'][this.e.value]); + this.customDrawSegment(-17, -38, 17, -38, ['lightgrey', 'red'][this.a.value]); + this.customDrawSegment(-17, 0, 17, 0, ['lightgrey', 'red'][this.g.value]); + this.customDrawSegment(-15, 38, 17, 38, ['lightgrey', 'red'][this.d.value]); + ctx.beginPath(); + const dotColor = ['lightgrey', 'red'][this.dot.value] || 'lightgrey'; + ctx.strokeStyle = dotColor; + rect(ctx, xx + 22, yy + 42, 2, 2); + ctx.stroke(); + } +} + +/** + * @memberof SevenSegDisplay + * Help Tip + * @type {string} + * @category modules + */ +SevenSegDisplay.prototype.tooltipText = 'Seven Display ToolTip: Consists of 7+1 single bit inputs.'; + +/** + * @memberof SevenSegDisplay + * Help URL + * @type {string} + * @category modules + */ +SevenSegDisplay.prototype.helplink = 'https://docs.circuitverse.org/#/outputs?id=seven-segment-display'; +SevenSegDisplay.prototype.objectType = 'SevenSegDisplay'; diff --git a/src/modules/SixteenSegDisplay.js b/src/modules/SixteenSegDisplay.js new file mode 100755 index 0000000..da313aa --- /dev/null +++ b/src/modules/SixteenSegDisplay.js @@ -0,0 +1,131 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, rect, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * SixteenSegDisplay + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @category modules + */ +export default class SixteenSegDisplay extends CircuitElement { + constructor(x, y, scope = globalScope) { + super(x, y, scope, 'RIGHT', 16); + /* this is done in this.baseSetup() now + this.scope['SixteenSegDisplay'].push(this); + */ + this.fixedBitWidth = true; + this.directionFixed = true; + this.setDimensions(30, 50); + this.input1 = new Node(0, -50, 0, this, 16); + this.dot = new Node(0, 50, 0, this, 1); + this.direction = 'RIGHT'; + } + + /** + * @memberof SixteenSegDisplay + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + nodes: { + input1: findNode(this.input1), + dot: findNode(this.dot), + }, + }; + return data; + } + + /** + * @memberof SixteenSegDisplay + * function to draw element + */ + customDrawSegment(x1, y1, x2, y2, color) { + if (color === undefined) color = 'lightgrey'; + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = correctWidth(4); + const xx = this.x; + const yy = this.y; + moveTo(ctx, x1, y1, xx, yy, this.direction); + lineTo(ctx, x2, y2, xx, yy, this.direction); + ctx.closePath(); + ctx.stroke(); + } + + /** + * @memberof SixteenSegDisplay + * function to draw element + */ + customDrawSegmentSlant(x1, y1, x2, y2, color) { + if (color === undefined) color = 'lightgrey'; + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + moveTo(ctx, x1, y1, xx, yy, this.direction); + lineTo(ctx, x2, y2, xx, yy, this.direction); + ctx.closePath(); + ctx.stroke(); + } + + /** + * @memberof SixteenSegDisplay + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + const color = ['lightgrey', 'red']; + const { value } = this.input1; + this.customDrawSegment(-20, -38, 0, -38, ['lightgrey', 'red'][(value >> 15) & 1]);// a1 + this.customDrawSegment(20, -38, 0, -38, ['lightgrey', 'red'][(value >> 14) & 1]);// a2 + this.customDrawSegment(21.5, -2, 21.5, -36, ['lightgrey', 'red'][(value >> 13) & 1]);// b + this.customDrawSegment(21.5, 2, 21.5, 36, ['lightgrey', 'red'][(value >> 12) & 1]);// c + this.customDrawSegment(-20, 38, 0, 38, ['lightgrey', 'red'][(value >> 11) & 1]);// d1 + this.customDrawSegment(20, 38, 0, 38, ['lightgrey', 'red'][(value >> 10) & 1]);// d2 + this.customDrawSegment(-21.5, 2, -21.5, 36, ['lightgrey', 'red'][(value >> 9) & 1]);// e + this.customDrawSegment(-21.5, -36, -21.5, -2, ['lightgrey', 'red'][(value >> 8) & 1]);// f + this.customDrawSegment(-20, 0, 0, 0, ['lightgrey', 'red'][(value >> 7) & 1]);// g1 + this.customDrawSegment(20, 0, 0, 0, ['lightgrey', 'red'][(value >> 6) & 1]);// g2 + this.customDrawSegmentSlant(0, 0, -21, -37, ['lightgrey', 'red'][(value >> 5) & 1]);// h + this.customDrawSegment(0, -2, 0, -36, ['lightgrey', 'red'][(value >> 4) & 1]);// i + this.customDrawSegmentSlant(0, 0, 21, -37, ['lightgrey', 'red'][(value >> 3) & 1]);// j + this.customDrawSegmentSlant(0, 0, 21, 37, ['lightgrey', 'red'][(value >> 2) & 1]);// k + this.customDrawSegment(0, 2, 0, 36, ['lightgrey', 'red'][(value >> 1) & 1]);// l + this.customDrawSegmentSlant(0, 0, -21, 37, ['lightgrey', 'red'][(value >> 0) & 1]);// m + ctx.beginPath(); + const dotColor = ['lightgrey', 'red'][this.dot.value] || 'lightgrey'; + ctx.strokeStyle = dotColor; + rect(ctx, xx + 22, yy + 42, 2, 2); + ctx.stroke(); + } +} + +/** + * @memberof SixteenSegDisplay + * Help Tip + * @type {string} + * @category modules + */ +SixteenSegDisplay.prototype.tooltipText = 'Sixteen Display ToolTip: Consists of 16+1 bit inputs.'; + +/** + * @memberof SixteenSegDisplay + * Help URL + * @type {string} + * @category modules + */ +SixteenSegDisplay.prototype.helplink = 'https://docs.circuitverse.org/#/outputs?id=sixteen-segment-display'; +SixteenSegDisplay.prototype.objectType = 'SixteenSegDisplay'; diff --git a/src/modules/Splitter.js b/src/modules/Splitter.js new file mode 100755 index 0000000..f7e5595 --- /dev/null +++ b/src/modules/Splitter.js @@ -0,0 +1,230 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText2, +} from '../canvasApi'; + +function extractBits(num, start, end) { + return (num << (32 - end)) >>> (32 - (end - start + 1)); +} + +/** + * @class + * Splitter + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @param {number=} bitWidthSplit - number of input nodes + * @category modules + */ +export default class Splitter extends CircuitElement { + constructor( + x, + y, + scope = globalScope, + dir = 'RIGHT', + bitWidth = undefined, + bitWidthSplit = undefined, + ) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Splitter'].push(this); + */ + this.rectangleObject = false; + + this.bitWidthSplit = bitWidthSplit || prompt('Enter bitWidth Split').split(' ').filter((lambda) => lambda !== '').map((lambda) => parseInt(lambda, 10) || 1); + this.splitCount = this.bitWidthSplit.length; + + this.setDimensions(10, (this.splitCount - 1) * 10 + 10); + this.yOffset = (this.splitCount / 2 - 1) * 20; + + this.inp1 = new Node(-10, 10 + this.yOffset, 0, this, this.bitWidth); + + this.outputs = []; + // this.prevOutValues=new Array(this.splitCount) + for (let i = 0; i < this.splitCount; i++) { this.outputs.push(new Node(20, i * 20 - this.yOffset - 20, 0, this, this.bitWidthSplit[i])); } + + this.prevInpValue = undefined; + } + + /** + * @memberof Splitter + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + + constructorParamaters: [this.direction, this.bitWidth, this.bitWidthSplit], + nodes: { + outputs: this.outputs.map(findNode), + inp1: findNode(this.inp1), + }, + }; + return data; + } + + /** + * @memberof Splitter + * fn to remove proporgation delay. + * @return {JSON} + */ + removePropagation() { + if (this.inp1.value === undefined) { + let i = 0; + for (i = 0; i < this.outputs.length; i++) { // False Hit + if (this.outputs[i].value === undefined) return; + } + for (i = 0; i < this.outputs.length; i++) { + if (this.outputs[i].value !== undefined) { + this.outputs[i].value = undefined; + simulationArea.simulationQueue.add(this.outputs[i]); + } + } + } else if (this.inp1.value !== undefined) { + this.inp1.value = undefined; + simulationArea.simulationQueue.add(this.inp1); + } + this.prevInpValue = undefined; + } + + /** + * @memberof Splitter + * Checks if the element is resolvable + * @return {boolean} + */ + isResolvable() { + let resolvable = false; + if (this.inp1.value !== this.prevInpValue) { + if (this.inp1.value !== undefined) return true; + return false; + } + let i; + for (i = 0; i < this.splitCount; i++) { if (this.outputs[i].value === undefined) break; } + if (i === this.splitCount) resolvable = true; + return resolvable; + } + + /** + * @memberof Splitter + * resolve output values based on inputData + */ + resolve() { + if (this.isResolvable() === false) { + return; + } + if (this.inp1.value !== undefined && this.inp1.value !== this.prevInpValue) { + let bitCount = 1; + for (let i = 0; i < this.splitCount; i++) { + const bitSplitValue = extractBits(this.inp1.value, bitCount, bitCount + this.bitWidthSplit[i] - 1); + if (this.outputs[i].value !== bitSplitValue) { + if (this.outputs[i].value !== bitSplitValue) { + this.outputs[i].value = bitSplitValue; + simulationArea.simulationQueue.add(this.outputs[i]); + } + } + bitCount += this.bitWidthSplit[i]; + } + } else { + let n = 0; + for (let i = this.splitCount - 1; i >= 0; i--) { + n <<= this.bitWidthSplit[i]; + n += this.outputs[i].value; + } + if (this.inp1.value !== n) { + this.inp1.value = n; + simulationArea.simulationQueue.add(this.inp1); + } + // else if (this.inp1.value !== n) { + // console.log("CONTENTION"); + // } + } + this.prevInpValue = this.inp1.value; + } + + /** + * @memberof Splitter + * fn to reset values of splitter + */ + reset() { + this.prevInpValue = undefined; + } + + /** + * @memberof Splitter + * fn to process verilog of the element + * @return {JSON} + */ + processVerilog() { + // console.log(this.inp1.verilogLabel +":"+ this.outputs[0].verilogLabel); + if (this.inp1.verilogLabel !== '' && this.outputs[0].verilogLabel === '') { + let bitCount = 0; + for (let i = 0; i < this.splitCount; i++) { + // let bitSplitValue = extractBits(this.inp1.value, bitCount, bitCount + this.bitWidthSplit[i] - 1); + if (this.bitWidthSplit[i] > 1) { const label = `${this.inp1.verilogLabel}[ ${bitCount + this.bitWidthSplit[i] - 1}:${bitCount}]`; } else { const label = `${this.inp1.verilogLabel}[${bitCount}]`; } + if (this.outputs[i].verilogLabel !== label) { + this.outputs[i].verilogLabel = label; + this.scope.stack.push(this.outputs[i]); + } + bitCount += this.bitWidthSplit[i]; + } + } else if (this.inp1.verilogLabel === '' && this.outputs[0].verilogLabel !== '') { + const label = `{${this.outputs.map((x) => x.verilogLabel).join(',')}}`; + // console.log("HIT",label) + if (this.inp1.verilogLabel !== label) { + this.inp1.verilogLabel = label; + this.scope.stack.push(this.inp1); + } + } + } + + /** + * @memberof Splitter + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = ['black', 'brown'][((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) + 0]; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + moveTo(ctx, -10, 10 + this.yOffset, xx, yy, this.direction); + lineTo(ctx, 0, 0 + this.yOffset, xx, yy, this.direction); + lineTo(ctx, 0, -20 * (this.splitCount - 1) + this.yOffset, xx, yy, this.direction); + let bitCount = 0; + for (let i = this.splitCount - 1; i >= 0; i--) { + moveTo(ctx, 0, -20 * i + this.yOffset, xx, yy, this.direction); + lineTo(ctx, 20, -20 * i + this.yOffset, xx, yy, this.direction); + } + ctx.stroke(); + ctx.beginPath(); + ctx.fillStyle = 'black'; + for (let i = this.splitCount - 1; i >= 0; i--) { + fillText2(ctx, `${bitCount}:${bitCount + this.bitWidthSplit[this.splitCount - i - 1]}`, 12, -20 * i + this.yOffset + 10, xx, yy, this.direction); + bitCount += this.bitWidthSplit[this.splitCount - i - 1]; + } + ctx.fill(); + } +} + +/** + * @memberof Splitter + * Help Tip + * @type {string} + * @category modules + */ +Splitter.prototype.tooltipText = 'Splitter ToolTip: Split multiBit Input into smaller bitwidths or vice versa.'; + +/** + * @memberof Splitter + * Help URL + * @type {string} + * @category modules + */ +Splitter.prototype.helplink = 'https://docs.circuitverse.org/#/splitter'; +Splitter.prototype.objectType = 'Splitter'; diff --git a/src/modules/SquareRGBLed.js b/src/modules/SquareRGBLed.js new file mode 100755 index 0000000..1f12eb1 --- /dev/null +++ b/src/modules/SquareRGBLed.js @@ -0,0 +1,154 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, rect2, +} from '../canvasApi'; + +/** + * @class + * SquareRGBLed + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} pinLength - pins per node. + * @category modules + */ +export default class SquareRGBLed extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'UP', pinLength = 1) { + super(x, y, scope, dir, 8); + /* this is done in this.baseSetup() now + this.scope['SquareRGBLed'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 15); + this.pinLength = pinLength === undefined ? 1 : pinLength; + const nodeX = -10 - 10 * pinLength; + this.inp1 = new Node(nodeX, -10, 0, this, 8, 'R'); + this.inp2 = new Node(nodeX, 0, 0, this, 8, 'G'); + this.inp3 = new Node(nodeX, 10, 0, this, 8, 'B'); + this.inp = [this.inp1, this.inp2, this.inp3]; + this.labelDirection = 'UP'; + this.fixedBitWidth = true; + + // eslint-disable-next-line no-shadow + this.changePinLength = function (pinLength) { + if (pinLength === undefined) return; + pinLength = parseInt(pinLength, 10); + if (pinLength < 0 || pinLength > 1000) return; + + // Calculate the new position of the LED, so the nodes will stay in the same place. + const diff = 10 * (pinLength - this.pinLength); + // eslint-disable-next-line no-nested-ternary + const diffX = this.direction === 'LEFT' ? -diff : this.direction === 'RIGHT' ? diff : 0; + // eslint-disable-next-line no-nested-ternary + const diffY = this.direction === 'UP' ? -diff : this.direction === 'DOWN' ? diff : 0; + + // Build a new LED with the new values; preserve label properties too. + const obj = new SquareRGBLed(this.x + diffX, this.y + diffY, this.scope, this.direction, pinLength); + obj.label = this.label; + obj.labelDirection = this.labelDirection; + + this.cleanDelete(); + simulationArea.lastSelected = obj; + return obj; + }; + + this.mutableProperties = { + pinLength: { + name: 'Pin Length', + type: 'number', + max: '1000', + min: '0', + func: 'changePinLength', + }, + }; + } + + /** + * @memberof SquareRGBLed + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.pinLength], + nodes: { + inp1: findNode(this.inp1), + inp2: findNode(this.inp2), + inp3: findNode(this.inp3), + }, + }; + return data; + } + + /** + * @memberof SquareRGBLed + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + const r = this.inp1.value; + const g = this.inp2.value; + const b = this.inp3.value; + + const colors = ['rgb(174,20,20)', 'rgb(40,174,40)', 'rgb(0,100,255)']; + for (let i = 0; i < 3; i++) { + const x = -10 - 10 * this.pinLength; + const y = i * 10 - 10; + ctx.lineWidth = correctWidth(3); + + // A gray line, which makes it easy on the eyes when the pin length is large + ctx.beginPath(); + ctx.lineCap = 'butt'; + ctx.strokeStyle = 'rgb(227, 228, 229)'; + moveTo(ctx, -15, y, xx, yy, this.direction); + lineTo(ctx, x + 10, y, xx, yy, this.direction); + ctx.stroke(); + + // A colored line, so people know which pin does what. + ctx.lineCap = 'round'; + ctx.beginPath(); + ctx.strokeStyle = colors[i]; + moveTo(ctx, x + 10, y, xx, yy, this.direction); + lineTo(ctx, x, y, xx, yy, this.direction); + ctx.stroke(); + } + + ctx.strokeStyle = '#d3d4d5'; + ctx.fillStyle = (r === undefined && g === undefined && b === undefined) ? 'rgb(227, 228, 229)' : `rgb(${r || 0}, ${g || 0}, ${b || 0})`; + ctx.lineWidth = correctWidth(1); + ctx.beginPath(); + rect2(ctx, -15, -15, 30, 30, xx, yy, this.direction); + ctx.stroke(); + + if ((this.hover && !simulationArea.shiftDown) + || simulationArea.lastSelected === this + || simulationArea.multipleObjectSelections.contains(this)) { + ctx.fillStyle = 'rgba(255, 255, 32)'; + } + + ctx.fill(); + } +} + +/** + * @memberof SquareRGBLed + * Help Tip + * @type {string} + * @category modules + */ +SquareRGBLed.prototype.tooltipText = 'Square RGB Led ToolTip: RGB Led inputs 8 bit values for the colors RED, GREEN and BLUE.'; + +/** + * @memberof SquareRGBLed + * Help URL + * @type {string} + * @category modules + */ +SquareRGBLed.prototype.helplink = 'https://docs.circuitverse.org/#/outputs?id=square-rgb-led'; +SquareRGBLed.prototype.objectType = 'SquareRGBLed'; diff --git a/src/modules/Stepper.js b/src/modules/Stepper.js new file mode 100755 index 0000000..ffd758d --- /dev/null +++ b/src/modules/Stepper.js @@ -0,0 +1,99 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { fillText } from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * Stepper + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bitwidth of element + * @category modules + */ +export default class Stepper extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 8) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Stepper'].push(this); + */ + this.setDimensions(20, 20); + + this.output1 = new Node(20, 0, 1, this, bitWidth); + this.state = 0; + } + + /** + * @memberof Stepper + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth], + nodes: { + output1: findNode(this.output1), + }, + values: { + state: this.state, + }, + }; + return data; + } + + /** + * @memberof Stepper + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + + ctx.beginPath(); + ctx.font = '20px Georgia'; + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + fillText(ctx, this.state.toString(16), this.x, this.y + 5); + ctx.fill(); + } + + /** + * @memberof Stepper + * resolve output values based on inputData + */ + resolve() { + this.state = Math.min(this.state, (1 << this.bitWidth) - 1); + this.output1.value = this.state; + simulationArea.simulationQueue.add(this.output1); + } + + /** + * Listener function for increasing value of state + * @memberof Stepper + * @param {string} key - the key pressed + */ + keyDown2(key) { + // console.log(key); + if (this.state < (1 << this.bitWidth) && (key === '+' || key === '=')) this.state++; + if (this.state > 0 && (key === '_' || key === '-')) this.state--; + } +} + +/** + * @memberof Stepper + * Help Tip + * @type {string} + * @category modules + */ +Stepper.prototype.tooltipText = 'Stepper ToolTip: Increase/Decrease value by selecting the stepper and using +/- keys.'; + +/** + * @memberof Stepper + * Help URL + * @type {string} + * @category modules + */ +Stepper.prototype.helplink = 'https://docs.circuitverse.org/#/inputElements?id=stepper'; +Stepper.prototype.objectType = 'Stepper'; diff --git a/src/modules/Text.js b/src/modules/Text.js new file mode 100755 index 0000000..d00b412 --- /dev/null +++ b/src/modules/Text.js @@ -0,0 +1,144 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { rect2, fillText } from '../canvasApi'; +/** + * @class + * Text + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} label - label of element + * @param {number=} fontSize - font size + * @category modules + */ +export default class Text extends CircuitElement { + constructor(x, y, scope = globalScope, label = '', fontSize = 14) { + super(x, y, scope, 'RIGHT', 1); + /* this is done in this.baseSetup() now + this.scope['Text'].push(this); + */ + // this.setDimensions(15, 15); + this.fixedBitWidth = true; + this.directionFixed = true; + this.labelDirectionFixed = true; + this.setHeight(10); + this.setLabel(label); + this.setFontSize(fontSize); + } + + /** + * @memberof Text + * function for setting text inside the element + * @param {string=} str - the label + */ + setLabel(str = '') { + this.label = str; + const ctx = simulationArea.context; + ctx.font = `${this.fontSize}px Georgia`; + this.leftDimensionX = 10; + this.rightDimensionX = ctx.measureText(this.label).width + 10; + // console.log(this.leftDimensionX,this.rightDimensionX,ctx.measureText(this.label)) + } + + /** + * @memberof Text + * function for setting font size inside the element + * @param {number=} str - the font size + */ + setFontSize(fontSize = 14) { + this.fontSize = fontSize; + const ctx = simulationArea.context; + ctx.font = `${this.fontSize}px Georgia`; + this.leftDimensionX = 10; + this.rightDimensionX = ctx.measureText(this.label).width + 10; + } + + /** + * @memberof Text + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.label, this.fontSize], + }; + return data; + } + + /** + * @memberof Text + * Listener function for Text Box + * @param {string} key - the label + */ + keyDown(key) { + if (key.length === 1) { + if (this.label === 'Enter Text Here') { this.setLabel(key); } else { this.setLabel(this.label + key); } + } else if (key === 'Backspace') { + if (this.label === 'Enter Text Here') { this.setLabel(''); } else { this.setLabel(this.label.slice(0, -1)); } + } + } + + /** + * @memberof Text + * Function for drawing text box + */ + draw() { + if (this.label.length === 0 && simulationArea.lastSelected !== this) this.delete(); + const ctx = simulationArea.context; + ctx.strokeStyle = 'black'; + ctx.lineWidth = 1; + const xx = this.x; + const yy = this.y; + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) { + ctx.beginPath(); + ctx.fillStyle = 'white'; + const magicDimenstion = this.fontSize - 14; + rect2(ctx, -this.leftDimensionX, -this.upDimensionY - magicDimenstion, + this.leftDimensionX + this.rightDimensionX, + this.upDimensionY + this.downDimensionY + magicDimenstion, this.x, this.y, 'RIGHT'); + ctx.fillStyle = 'rgba(255, 255, 32,0.1)'; + ctx.fill(); + ctx.stroke(); + } + ctx.beginPath(); + ctx.textAlign = 'left'; + ctx.fillStyle = 'black'; + fillText(ctx, this.label, xx, yy + 5, this.fontSize); + ctx.fill(); + } +} + +/** + * @memberof Text + * Help Tip + * @type {string} + * @category modules + */ +Text.prototype.tooltipText = 'Text ToolTip: Use this to document your circuit.'; + +/** + * @memberof Text + * Help URL + * @type {string} + * @category modules + */ +Text.prototype.helplink = 'https://docs.circuitverse.org/#/annotation?id=adding-labels'; + +/** + * @memberof Text + * Mutable properties of the element + * @type {JSON} + * @category modules + */ +Text.prototype.mutableProperties = { + fontSize: { + name: 'Font size: ', + type: 'number', + max: '84', + min: '14', + func: 'setFontSize', + }, +}; +Text.prototype.objectType = 'Text'; diff --git a/src/modules/TriState.js b/src/modules/TriState.js new file mode 100755 index 0000000..d732c74 --- /dev/null +++ b/src/modules/TriState.js @@ -0,0 +1,116 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * TriState + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class TriState extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['TriState'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 15); + + this.inp1 = new Node(-10, 0, 0, this); + this.output1 = new Node(20, 0, 1, this); + this.state = new Node(0, 0, 0, this, 1, 'Enable'); + } + + // TriState.prototype.propagationDelay=10000; + + /** + * @memberof TriState + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth], + nodes: { + output1: findNode(this.output1), + inp1: findNode(this.inp1), + state: findNode(this.state), + }, + }; + return data; + } + + /** + * @memberof TriState + * function to change bitwidth of the element + * @param {number} bitWidth - new bitwidth + */ + newBitWidth(bitWidth) { + this.inp1.bitWidth = bitWidth; + this.output1.bitWidth = bitWidth; + this.bitWidth = bitWidth; + } + + /** + * @memberof TriState + * resolve output values based on inputData + */ + resolve() { + if (this.isResolvable() === false) { + return; + } + + if (this.state.value === 1) { + if (this.output1.value !== this.inp1.value) { + this.output1.value = this.inp1.value; // >>>0)<<(32-this.bitWidth))>>>(32-this.bitWidth); + simulationArea.simulationQueue.add(this.output1); + } + simulationArea.contentionPending.clean(this); + } else if (this.output1.value !== undefined && !simulationArea.contentionPending.contains(this)) { + this.output1.value = undefined; + simulationArea.simulationQueue.add(this.output1); + } + simulationArea.contentionPending.clean(this); + } + + /** + * @memberof TriState + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(3); + + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + ctx.fillStyle = 'white'; + moveTo(ctx, -10, -15, xx, yy, this.direction); + lineTo(ctx, 20, 0, xx, yy, this.direction); + lineTo(ctx, -10, 15, xx, yy, this.direction); + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + } +} + +/** + * @memberof TriState + * Help Tip + * @type {string} + * @category modules + */ +TriState.prototype.tooltipText = 'TriState ToolTip : Effectively removes the output from the circuit.'; +TriState.prototype.helplink = 'https://docs.circuitverse.org/#/miscellaneous?id=tri-state-buffer'; +TriState.prototype.objectType = 'TriState'; diff --git a/src/modules/Tunnel.js b/src/modules/Tunnel.js new file mode 100755 index 0000000..5521ca7 --- /dev/null +++ b/src/modules/Tunnel.js @@ -0,0 +1,255 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { correctWidth, rect2, fillText } from '../canvasApi'; +import plotArea from '../plotArea'; +/** + * @class + * Tunnel + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @param {string=} identifier - number of input nodes + * @category modules + */ +export default class Tunnel extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'LEFT', bitWidth = 1, identifier) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['Tunnel'].push(this); + */ + this.rectangleObject = false; + this.centerElement = true; + this.xSize = 10; + this.plotValues = []; + this.inp1 = new Node(0, 0, 0, this); + this.setIdentifier(identifier || 'T'); + this.setBounds(); + } + + /** + * @memberof Tunnel + * function to change direction of Tunnel + * @param {string} dir - new direction + */ + newDirection(dir) { + if (this.direction === dir) return; + this.direction = dir; + this.setBounds(); + } + + setBounds() { + let xRotate = 0; + let yRotate = 0; + if (this.direction === 'LEFT') { + xRotate = 0; + yRotate = 0; + } else if (this.direction === 'RIGHT') { + xRotate = 120 - this.xSize; + yRotate = 0; + } else if (this.direction === 'UP') { + xRotate = 60 - this.xSize / 2; + yRotate = -20; + } else { + xRotate = 60 - this.xSize / 2; + yRotate = 20; + } + + this.leftDimensionX = Math.abs(-120 + xRotate + this.xSize); + this.upDimensionY = Math.abs(-20 + yRotate); + this.rightDimensionX = Math.abs(xRotate); + this.downDimensionY = Math.abs(20 + yRotate); + + // rect2(ctx, -120 + xRotate + this.xSize, -20 + yRotate, 120 - this.xSize, 40, xx, yy, "RIGHT"); + } + + /** + * @memberof Tunnel + * function to set tunnel value + * @param {number} val - tunnel value + */ + setTunnelValue(val) { + this.inp1.value = val; + for (let i = 0; i < this.inp1.connections.length; i++) { + if (this.inp1.connections[i].value !== val) { + this.inp1.connections[i].value = val; + simulationArea.simulationQueue.add(this.inp1.connections[i]); + } + } + } + + /** + * @memberof Tunnel + * resolve output values based on inputData + */ + resolve() { + for (let i = 0; i < this.scope.tunnelList[this.identifier].length; i++) { + if (this.scope.tunnelList[this.identifier][i].inp1.value !== this.inp1.value) { + this.scope.tunnelList[this.identifier][i].setTunnelValue(this.inp1.value); + } + } + } + + /** + * @memberof Tunnel + * function to set tunnel value + * @param {Scope} scope - tunnel value + */ + updateScope(scope) { + this.scope = scope; + this.inp1.updateScope(scope); + this.setIdentifier(this.identifier); + // console.log("ShouldWork!"); + } + + /** + * @memberof Tunnel + * function to set plot value + */ + setPlotValue() { + const time = plotArea.stopWatch.ElapsedMilliseconds; + if (this.plotValues.length && this.plotValues[this.plotValues.length - 1][0] === time) { this.plotValues.pop(); } + + if (this.plotValues.length === 0) { + this.plotValues.push([time, this.inp1.value]); + return; + } + + if (this.plotValues[this.plotValues.length - 1][1] === this.inp1.value) { return; } + this.plotValues.push([time, this.inp1.value]); + } + + /** + * @memberof Tunnel + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth, this.identifier], + nodes: { + inp1: findNode(this.inp1), + }, + values: { + identifier: this.identifier, + }, + }; + return data; + } + + /** + * @memberof Tunnel + * function to set tunnel value + * @param {string=} id - id so that every link is unique + */ + setIdentifier(id = '') { + if (id.length === 0) return; + if (this.scope.tunnelList[this.identifier]) this.scope.tunnelList[this.identifier].clean(this); + this.identifier = id; + if (this.scope.tunnelList[this.identifier]) this.scope.tunnelList[this.identifier].push(this); + else this.scope.tunnelList[this.identifier] = [this]; + const len = this.identifier.length; + if (len === 1) this.xSize = 40; + else if (len > 1 && len < 4) this.xSize = 20; + else this.xSize = 0; + this.setBounds(); + } + + /** + * @memberof Tunnel + * delete the tunnel element + */ + delete() { + this.scope.Tunnel.clean(this); + this.scope.tunnelList[this.identifier].clean(this); + super.delete(); + this.scope.Tunnel.push(this); + } + + /** + * @memberof Tunnel + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = 'grey'; + ctx.fillStyle = '#fcfcfc'; + ctx.lineWidth = correctWidth(1); + const xx = this.x; + const yy = this.y; + + let xRotate = 0; + let yRotate = 0; + if (this.direction === 'LEFT') { + xRotate = 0; + yRotate = 0; + } else if (this.direction === 'RIGHT') { + xRotate = 120 - this.xSize; + yRotate = 0; + } else if (this.direction === 'UP') { + xRotate = 60 - this.xSize / 2; + yRotate = -20; + } else { + xRotate = 60 - this.xSize / 2; + yRotate = 20; + } + + rect2(ctx, -120 + xRotate + this.xSize, -20 + yRotate, 120 - this.xSize, 40, xx, yy, 'RIGHT'); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) { ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; } + ctx.fill(); + ctx.stroke(); + + ctx.font = '14px Georgia'; + this.xOff = ctx.measureText(this.identifier).width; + ctx.beginPath(); + rect2(ctx, -105 + xRotate + this.xSize, -11 + yRotate, this.xOff + 10, 23, xx, yy, 'RIGHT'); + ctx.fillStyle = '#eee'; + ctx.strokeStyle = '#ccc'; + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.textAlign = 'center'; + ctx.fillStyle = 'black'; + fillText(ctx, this.identifier, xx - 100 + this.xOff / 2 + xRotate + this.xSize, yy + 6 + yRotate, 14); + ctx.fill(); + + ctx.beginPath(); + ctx.font = '30px Georgia'; + ctx.textAlign = 'center'; + ctx.fillStyle = ['blue', 'red'][+(this.inp1.value === undefined)]; + if (this.inp1.value !== undefined) { fillText(ctx, this.inp1.value.toString(16), xx - 23 + xRotate, yy + 8 + yRotate, 25); } else { fillText(ctx, 'x', xx - 23 + xRotate, yy + 8 + yRotate, 25); } + ctx.fill(); + } +} + +/** + * @memberof Tunnel + * Help Tip + * @type {string} + * @category modules + */ +Tunnel.prototype.tooltipText = 'Tunnel ToolTip : Tunnel Selected.'; +Tunnel.prototype.helplink = 'https://docs.circuitverse.org/#/miscellaneous?id=tunnel'; + +Tunnel.prototype.overrideDirectionRotation = true; + +/** + * @memberof Tunnel + * Mutable properties of the element + * @type {JSON} + * @category modules + */ +Tunnel.prototype.mutableProperties = { + identifier: { + name: 'Debug Flag identifier', + type: 'text', + maxlength: '5', + func: 'setIdentifier', + }, +}; +Tunnel.prototype.objectType = 'Tunnel'; diff --git a/src/modules/TwoComplement.js b/src/modules/TwoComplement.js new file mode 100755 index 0000000..8ccbf34 --- /dev/null +++ b/src/modules/TwoComplement.js @@ -0,0 +1,89 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * TwoComplement + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class TwoComplement extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['TwoComplement'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 15); + this.inp1 = new Node(-10, 0, 0, this, this.bitWidth, 'input stream'); + this.output1 = new Node(20, 0, 1, this, this.bitWidth, "2's complement"); + } + + /** + * @memberof TwoComplement + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth], + nodes: { + output1: findNode(this.output1), + inp1: findNode(this.inp1), + }, + }; + return data; + } + + /** + * @memberof TwoComplement + * resolve output values based on inputData + */ + resolve() { + if (this.isResolvable() === false) { + return; + } + let output = ((~this.inp1.value >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); + output += 1; + this.output1.value = ((output) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof TwoComplement + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = 'black'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + ctx.fillStyle = 'black'; + fillText(ctx, "2'", xx, yy, 10); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.beginPath(); + drawCircle2(ctx, 5, 0, 15, xx, yy, this.direction); + ctx.stroke(); + } +} + +/** + * @memberof TwoComplement + * Help Tip + * @type {string} + * @category modules + */ +TwoComplement.prototype.tooltipText = "Two's Complement Tooltip : Calculates the two's complement"; +TwoComplement.prototype.objectType = 'TwoComplement'; diff --git a/src/modules/VariableLed.js b/src/modules/VariableLed.js new file mode 100755 index 0000000..197a27d --- /dev/null +++ b/src/modules/VariableLed.js @@ -0,0 +1,100 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, arc, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * VariableLed + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @category modules + */ +export default class VariableLed extends CircuitElement { + constructor(x, y, scope = globalScope) { + // Calling base class constructor + + super(x, y, scope, 'UP', 8); + /* this is done in this.baseSetup() now + this.scope['VariableLed'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(10, 20); + this.inp1 = new Node(-40, 0, 0, this, 8); + this.directionFixed = true; + this.fixedBitWidth = true; + } + + /** + * @memberof VariableLed + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + nodes: { + inp1: findNode(this.inp1), + }, + }; + return data; + } + + /** + * @memberof VariableLed + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + + const xx = this.x; + const yy = this.y; + + ctx.strokeStyle = '#353535'; + ctx.lineWidth = correctWidth(3); + ctx.beginPath(); + moveTo(ctx, -20, 0, xx, yy, this.direction); + lineTo(ctx, -40, 0, xx, yy, this.direction); + ctx.stroke(); + const c = this.inp1.value; + const alpha = c / 255; + ctx.strokeStyle = '#090a0a'; + ctx.fillStyle = [`rgba(255,29,43,${alpha})`, 'rgba(227, 228, 229, 0.8)'][(c === undefined || c === 0) + 0]; + ctx.lineWidth = correctWidth(1); + + ctx.beginPath(); + + moveTo(ctx, -20, -9, xx, yy, this.direction); + lineTo(ctx, 0, -9, xx, yy, this.direction); + arc(ctx, 0, 0, 9, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); + lineTo(ctx, -20, 9, xx, yy, this.direction); + /* lineTo(ctx,-18,12,xx,yy,this.direction); + arc(ctx,0,0,Math.sqrt(468),((Math.PI/2) + Math.acos(12/Math.sqrt(468))),((-Math.PI/2) - Math.asin(18/Math.sqrt(468))),xx,yy,this.direction); + + */ + lineTo(ctx, -20, -9, xx, yy, this.direction); + ctx.stroke(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + } +} + +/** + * @memberof VariableLed + * Help Tip + * @type {string} + * @category modules + */ +VariableLed.prototype.tooltipText = 'Variable Led ToolTip: Variable LED inputs an 8 bit value and glows with a proportional intensity.'; + +/** + * @memberof VariableLed + * Help URL + * @type {string} + * @category modules + */ +VariableLed.prototype.helplink = 'https://docs.circuitverse.org/#/outputs?id=variable-led'; +VariableLed.prototype.objectType = 'VariableLed'; diff --git a/src/modules/XnorGate.js b/src/modules/XnorGate.js new file mode 100755 index 0000000..5344bc4 --- /dev/null +++ b/src/modules/XnorGate.js @@ -0,0 +1,147 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, bezierCurveTo, moveTo, arc2, drawCircle2, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * XnorGate + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} inputLength - number of input nodes + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class XnorGate extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', inputs = 2, bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['XnorGate'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 20); + + this.inp = []; + this.inputSize = inputs; + + if (inputs % 2 === 1) { + for (let i = 0; i < inputs / 2 - 1; i++) { + const a = new Node(-20, -10 * (i + 1), 0, this); + this.inp.push(a); + } + let a = new Node(-20, 0, 0, this); + this.inp.push(a); + for (let i = inputs / 2 + 1; i < inputs; i++) { + a = new Node(-20, 10 * (i + 1 - inputs / 2 - 1), 0, this); + this.inp.push(a); + } + } else { + for (let i = 0; i < inputs / 2; i++) { + const a = new Node(-20, -10 * (i + 1), 0, this); + this.inp.push(a); + } + for (let i = inputs / 2; i < inputs; i++) { + const a = new Node(-20, 10 * (i + 1 - inputs / 2), 0, this); + this.inp.push(a); + } + } + this.output1 = new Node(30, 0, 1, this); + } + + /** + * @memberof XnorGate + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.inputSize, this.bitWidth], + nodes: { + inp: this.inp.map(findNode), + output1: findNode(this.output1), + }, + }; + return data; + } + + /** + * @memberof XnorGate + * resolve output values based on inputData + */ + resolve() { + let result = this.inp[0].value || 0; + if (this.isResolvable() === false) { + return; + } + for (let i = 1; i < this.inputSize; i++) result ^= (this.inp[i].value || 0); + result = ((~result >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth); + this.output1.value = result; + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof XnorGate + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(3); + + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + ctx.fillStyle = 'white'; + moveTo(ctx, -10, -20, xx, yy, this.direction, true); + bezierCurveTo(0, -20, +15, -10, 20, 0, xx, yy, this.direction); + bezierCurveTo(0 + 15, 0 + 10, 0, 0 + 20, -10, +20, xx, yy, this.direction); + bezierCurveTo(0, 0, 0, 0, -10, -20, xx, yy, this.direction); + // arc(ctx, 0, 0, -20, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + arc2(ctx, -35, 0, 25, 1.70 * (Math.PI), 0.30 * (Math.PI), xx, yy, this.direction); + ctx.stroke(); + ctx.beginPath(); + drawCircle2(ctx, 25, 0, 5, xx, yy, this.direction); + ctx.stroke(); + } +} + +/** + * @memberof XnorGate + * @type {boolean} + * @category modules + */ +XnorGate.prototype.alwaysResolve = true; + +/** + * @memberof XnorGate + * Help Tip + * @type {string} + * @category modules + */ +XnorGate.prototype.tooltipText = 'Xnor Gate ToolTip : Logical complement of the XOR gate'; + +/** + * @memberof XnorGate + * function to change input nodes of the element + * @category modules + */ +XnorGate.prototype.changeInputSize = changeInputSize; + +/** + * @memberof XnorGate + * @type {string} + * @category modules + */ +XnorGate.prototype.verilogType = 'xnor'; +XnorGate.prototype.helplink = 'https://docs.circuitverse.org/#/gates?id=xnor-gate'; +XnorGate.prototype.objectType = 'XnorGate'; diff --git a/src/modules/XorGate.js b/src/modules/XorGate.js new file mode 100755 index 0000000..8566614 --- /dev/null +++ b/src/modules/XorGate.js @@ -0,0 +1,145 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, bezierCurveTo, moveTo, arc2, +} from '../canvasApi'; +import { changeInputSize } from '../modules'; +/** + * @class + * XorGate + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} inputs - number of input nodes + * @param {number=} bitWidth - bit width per node. + * @category modules + */ +export default class XorGate extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', inputs = 2, bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* this is done in this.baseSetup() now + this.scope['XorGate'].push(this); + */ + this.rectangleObject = false; + this.setDimensions(15, 20); + + this.inp = []; + this.inputSize = inputs; + + if (inputs % 2 === 1) { + for (let i = 0; i < inputs / 2 - 1; i++) { + const a = new Node(-20, -10 * (i + 1), 0, this); + this.inp.push(a); + } + let a = new Node(-20, 0, 0, this); + this.inp.push(a); + for (let i = inputs / 2 + 1; i < inputs; i++) { + a = new Node(-20, 10 * (i + 1 - inputs / 2 - 1), 0, this); + this.inp.push(a); + } + } else { + for (let i = 0; i < inputs / 2; i++) { + const a = new Node(-20, -10 * (i + 1), 0, this); + this.inp.push(a); + } + for (let i = inputs / 2; i < inputs; i++) { + const a = new Node(-20, 10 * (i + 1 - inputs / 2), 0, this); + this.inp.push(a); + } + } + this.output1 = new Node(20, 0, 1, this); + } + + /** + * @memberof XorGate + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + // //console.log(this.scope.allNodes); + const data = { + constructorParamaters: [this.direction, this.inputSize, this.bitWidth], + nodes: { + inp: this.inp.map(findNode), + output1: findNode(this.output1), + }, + }; + return data; + } + + /** + * @memberof XorGate + * resolve output values based on inputData + */ + resolve() { + let result = this.inp[0].value || 0; + if (this.isResolvable() === false) { + return; + } + for (let i = 1; i < this.inputSize; i++) result ^= (this.inp[i].value || 0); + + this.output1.value = result; + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof XorGate + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(3); + + const xx = this.x; + const yy = this.y; + ctx.beginPath(); + ctx.fillStyle = 'white'; + moveTo(ctx, -10, -20, xx, yy, this.direction, true); + bezierCurveTo(0, -20, +15, -10, 20, 0, xx, yy, this.direction); + bezierCurveTo(0 + 15, 0 + 10, 0, 0 + 20, -10, +20, xx, yy, this.direction); + bezierCurveTo(0, 0, 0, 0, -10, -20, xx, yy, this.direction); + // arc(ctx, 0, 0, -20, (-Math.PI / 2), (Math.PI / 2), xx, yy, this.direction); + ctx.closePath(); + if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + arc2(ctx, -35, 0, 25, 1.70 * (Math.PI), 0.30 * (Math.PI), xx, yy, this.direction); + ctx.stroke(); + } +} + +/** + * @memberof XorGate + * Help Tip + * @type {string} + * @category modules + */ +XorGate.prototype.tooltipText = 'Xor Gate Tooltip : Implements an exclusive OR.'; + +/** + * @memberof XorGate + * @type {boolean} + * @category modules + */ +XorGate.prototype.alwaysResolve = true; + +/** + * @memberof XorGate + * function to change input nodes of the element + * @category modules + */ +XorGate.prototype.changeInputSize = changeInputSize; + +/** + * @memberof XorGate + * @type {string} + * @category modules + */ +XorGate.prototype.verilogType = 'xor'; +XorGate.prototype.helplink = 'https://docs.circuitverse.org/#/gates?id=xor-gate'; +XorGate.prototype.objectType = 'XorGate'; diff --git a/src/node.js b/src/node.js index df06616..b33db8e 100644 --- a/src/node.js +++ b/src/node.js @@ -1,21 +1,37 @@ -import { drawCircle, drawLine } from "./canvasApi"; -import { simulationArea } from "./simulationArea"; -import { distance } from "./utils"; -import { renderCanvas,scheduleUpdate } from "./engine"; -import Wire from "./wire" - -function constructNodeConnections(node, data) { - for (var i = 0; i < data["connections"].length; i++) - if (!node.connections.contains(node.scope.allNodes[data["connections"][i]])) node.connect(node.scope.allNodes[data["connections"][i]]); +/* eslint-disable import/no-cycle */ +import { drawCircle, drawLine, arc } from './canvasApi'; +import simulationArea from './simulationArea'; +import { distance, showError } from './utils'; +import { + renderCanvas, scheduleUpdate, wireToBeCheckedSet, + updateSimulationSet, updateCanvasSet, forceResetNodesSet, + canvasMessageData, +} from './engine'; +import Wire from './wire'; +import { createNodeGet, createNodeSet, stopWireSet } from './listeners'; + +/** +* Constructs all the connections of Node node + * @param {Node} node - node to be constructed + * @param {JSON} data - the saved data which is used to load + * @category node + */ +export function constructNodeConnections(node, data) { + for (let i = 0; i < data.connections.length; i++) { if (!node.connections.contains(node.scope.allNodes[data.connections[i]])) node.connect(node.scope.allNodes[data.connections[i]]); } } -//Fn to replace node by node @ index in global Node List - used when loading -function replace(node, index) { +/** + * Fn to replace node by node @ index in global Node List - used when loading + * @param {Node} node - node to be replaced + * @param {number} index - index of node to be replaced + * @category node + */ +export function replace(node, index) { if (index == -1) { return node; } - var scope = node.scope; - var parent = node.parent; + const { scope } = node; + const { parent } = node; parent.nodeList.clean(node); node.delete(); node = scope.allNodes[index]; @@ -24,71 +40,103 @@ function replace(node, index) { node.updateRotation(); return node; } -function rotate(x1, y1, dir){ - if (dir == "LEFT") - return [-x1, y1]; else if (dir == "DOWN") - return [y1, x1]; else if (dir == "UP") - return [y1, -x1]; else - return [x1, y1]; +function rotate(x1, y1, dir) { + if (dir == 'LEFT') { return [-x1, y1]; } if (dir == 'DOWN') { return [y1, x1]; } if (dir == 'UP') { return [y1, -x1]; } return [x1, y1]; } -function extractBits(num, start, end) { +export function extractBits(num, start, end) { return (num << (32 - end)) >>> (32 - (end - start + 1)); } -function bin2dec(binString) { +export function bin2dec(binString) { return parseInt(binString, 2); } -function dec2bin(dec, bitWidth = undefined) { +export function dec2bin(dec, bitWidth = undefined) { // only for positive nos - var bin = (dec).toString(2); + const bin = (dec).toString(2); if (bitWidth == undefined) return bin; return '0'.repeat(bitWidth - bin.length) + bin; } -//find Index of a node +/** + * find Index of a node + * @param {Node} x - Node to be dound + * @category node + */ export function findNode(x) { return x.scope.allNodes.indexOf(x); } -function loadNode(data, scope) { - var n = new Node(data["x"], data["y"], data["type"], scope.root, data["bitWidth"], data["label"]); +/** + * function makes a node according to data providede + * @param {JSON} data - the data used to load a Project + * @param {Scope} scope - scope to which node has to be loaded + * @category node + */ +export function loadNode(data, scope) { + const n = new Node(data.x, data.y, data.type, scope.root, data.bitWidth, data.label); } -//get Node in index x in scope and set parent +/** + * get Node in index x in scope and set parent + * @param {Node} x - the desired node + * @param {Scope} scope - the scope + * @param {CircuitElement} parent - The parent of node + * @category node + */ function extractNode(x, scope, parent) { - var n = scope.allNodes[x]; + const n = scope.allNodes[x]; n.parent = parent; return n; } -//output node=1 -//input node=0 -//intermediate node =2 - -const NODE_OUTPUT = 1; -const NODE_INPUT = 0; -const NODE_INTERMEDIATE = 2; - -export class Node { - constructor(x, y, type, parent, bitWidth = undefined, label = "") { - +// output node=1 +// input node=0 +// intermediate node =2 + +window.NODE_INPUT = 0; +window.NODE_OUTPUT = 1; +window.NODE_INTERMEDIATE = 2; +/** + * used to give id to a node. + * @type {number} + * @category node + */ +let uniqueIdCounter = 10; + +/** + * This class is responsible for all the Nodes.Nodes are connected using Wires + * Nodes are of 3 types; + * NODE_INPUT = 0; + * NODE_OUTPUT = 1; + * NODE_INTERMEDIATE = 2; + * Input and output nodes belong to some CircuitElement(it's parent) + * @param {number} x - x coord of Node + * @param {number} y - y coord of Node + * @param {number} type - type of node + * @param {CircuitElement} parent - parent element + * @param {?number} bitWidth - the bits of node in input and output nodes + * @param {string=} label - label for a node + * @category node + */ +export default class Node { + constructor(x, y, type, parent, bitWidth = undefined, label = '') { // Should never raise, but just in case - if(isNaN(x) || isNaN(y)){ + if (isNaN(x) || isNaN(y)) { this.delete(); - showError("Fatal error occurred"); + showError('Fatal error occurred'); return; } - forceResetNodes = true; + forceResetNodesSet(true); - this.objectType = "Node"; - this.id = 'node' + uniqueIdCounter; + this.objectType = 'Node'; + this.subcircuitOverride = false; + this.id = `node${uniqueIdCounter}`; uniqueIdCounter++; this.parent = parent; - if (type != 2 && this.parent.nodeList !== undefined) - this.parent.nodeList.push(this); + if (type != 2 && this.parent.nodeList !== undefined) { this.parent.nodeList.push(this); } if (bitWidth == undefined) { this.bitWidth = parent.bitWidth; @@ -111,15 +159,21 @@ export class Node { this.hover = false; this.wasClicked = false; this.scope = this.parent.scope; + /** + * @type {string} + * value of this.prev is + * 'a' : whenever a node is not being dragged this.prev is 'a' + * 'x' : when node is being dragged horizontally + * 'y' : when node is being dragged vertically + */ this.prev = 'a'; this.count = 0; this.highlighted = false; - //This fn is called during rotations and setup + // This fn is called during rotations and setup this.refresh(); - if (this.type == 2) - this.parent.scope.nodes.push(this); + if (this.type == 2) { this.parent.scope.nodes.push(this); } this.parent.scope.allNodes.push(this); @@ -127,14 +181,20 @@ export class Node { inQueue: false, time: undefined, index: undefined, - } - + }; } + /** + * @param {string} - new label + * Function to set label + */ setLabel(label) { - this.label = label; //|| ""; + this.label = label; // || ""; } + /** + * function to convert a node to intermediate node + */ converToIntermediate() { this.type = 2; this.x = this.absX(); @@ -143,173 +203,189 @@ export class Node { this.scope.nodes.push(this); } + /** + * Helper fuction to move a node. Sets up some variable which help in changing node. + */ startDragging() { this.oldx = this.x; this.oldy = this.y; } + /** + * Helper fuction to move a node. + */ drag() { this.x = this.oldx + simulationArea.mouseX - simulationArea.mouseDownX; this.y = this.oldy + simulationArea.mouseY - simulationArea.mouseDownY; } + /** + * Funciton for saving a node + */ saveObject() { - if (this.type == 2) { this.leftx = this.x; this.lefty = this.y; } - var data = { + const data = { x: this.leftx, y: this.lefty, type: this.type, bitWidth: this.bitWidth, label: this.label, connections: [], - } - for (var i = 0; i < this.connections.length; i++) { - data["connections"].push(findNode(this.connections[i])); + }; + for (let i = 0; i < this.connections.length; i++) { + data.connections.push(findNode(this.connections[i])); } return data; } + /** + * helper function to help rotating parent + */ updateRotation() { - var x, y; + let x; var + y; [x, y] = rotate(this.leftx, this.lefty, this.parent.direction); this.x = x; this.y = y; } + /** + * Refreshes a node after roation of parent + */ refresh() { - this.updateRotation(); - for (var i = 0; i < this.connections.length; i++) { + for (let i = 0; i < this.connections.length; i++) { this.connections[i].connections.clean(this); } this.connections = []; - } + /** + * gives absolute x position of the node + */ absX() { return this.x + this.parent.x; } + /** + * gives absolute y position of the node + */ absY() { return this.y + this.parent.y; } + /** + * update the scope of a node + */ updateScope(scope) { this.scope = scope; if (this.type == 2) this.parent = scope.root; - } + /** + * return true if node is connected or not connected but false if undefined. + */ isResolvable() { return this.value != undefined; } + /** + * function used to reset the nodes + */ reset() { this.value = undefined; this.highlighted = false; } + /** + * function to connect two nodes. + */ connect(n) { - if (n == this) return; if (n.connections.contains(this)) return; - var w = new Wire(this, n, this.parent.scope); + const w = new Wire(this, n, this.parent.scope); this.connections.push(n); n.connections.push(this); - updateCanvas = true; - updateSimulation = true; + updateCanvasSet(true); + updateSimulationSet(true); scheduleUpdate(); } + /** + * connects but doesnt draw the wire between nodes + */ connectWireLess(n) { - if (n == this) return; if (n.connections.contains(this)) return; this.connections.push(n); n.connections.push(this); - updateCanvas = true; - updateSimulation = true; + updateCanvasSet(true); + updateSimulationSet(true); scheduleUpdate(); } + /** + * disconnecting two nodes connected wirelessly + */ disconnectWireLess(n) { - this.connections.clean(n); n.connections.clean(this); } + /** + * function to resolve a node + */ resolve() { - // Remove Propogation of values (TriState) if (this.value == undefined) { - - - for (var i = 0; i < this.connections.length; i++) { + for (let i = 0; i < this.connections.length; i++) { if (this.connections[i].value !== undefined) { this.connections[i].value = undefined; simulationArea.simulationQueue.add(this.connections[i]); - } } if (this.type == NODE_INPUT) { - if (this.parent.objectType == "Splitter") { + if (this.parent.objectType == 'Splitter') { this.parent.removePropagation(); } else - if (this.parent.isResolvable()) - simulationArea.simulationQueue.add(this.parent); - else - this.parent.removePropagation(); - - + if (this.parent.isResolvable()) { simulationArea.simulationQueue.add(this.parent); } else { this.parent.removePropagation(); } } if (this.type == NODE_OUTPUT && !this.subcircuitOverride) { if (this.parent.isResolvable() && !this.parent.queueProperties.inQueue) { - if (this.parent.objectType == "TriState") { - if (this.parent.state.value) - simulationArea.simulationQueue.add(this.parent); + if (this.parent.objectType == 'TriState') { + if (this.parent.state.value) { simulationArea.simulationQueue.add(this.parent); } } else { simulationArea.simulationQueue.add(this.parent); } - } - } return; } if (this.type == 0) { - - if (this.parent.isResolvable()) - simulationArea.simulationQueue.add(this.parent); - + if (this.parent.isResolvable()) { simulationArea.simulationQueue.add(this.parent); } } - for (var i = 0; i < this.connections.length; i++) { - - let node = this.connections[i]; + for (let i = 0; i < this.connections.length; i++) { + const node = this.connections[i]; if (node.value != this.value) { - - if (node.type == 1 && node.value != undefined && node.parent.objectType != "TriState" && !(node.subcircuitOverride && node.scope != this.scope)) { - + if (node.type == 1 && node.value != undefined && node.parent.objectType != 'TriState' && !(node.subcircuitOverride && node.scope != this.scope)) { this.highlighted = true; node.highlighted = true; - showError("Contention Error: " + this.value + " and " + node.value); + showError(`Contention Error: ${this.value} and ${node.value}`); } else if (node.bitWidth == this.bitWidth || node.type == 2) { - - if (node.parent.objectType == "TriState" && node.value != undefined && node.type == 1) { - if (node.parent.state.value) - simulationArea.contentionPending.push(node.parent); + if (node.parent.objectType == 'TriState' && node.value != undefined && node.type == 1) { + if (node.parent.state.value) { simulationArea.contentionPending.push(node.parent); } } node.bitWidth = this.bitWidth; @@ -318,13 +394,15 @@ export class Node { } else { this.highlighted = true; node.highlighted = true; - showError("BitWidth Error: " + this.bitWidth + " and " + node.bitWidth); + showError(`BitWidth Error: ${this.bitWidth} and ${node.bitWidth}`); } } } - } + /** + * this function checks if hover over the node + */ checkHover() { if (!simulationArea.mouseDown) { if (simulationArea.hover == this) { @@ -336,12 +414,10 @@ export class Node { } else if (!simulationArea.hover) { this.hover = this.isHover(); if (this.hover) { - simulationArea.hover = this; } else { this.showHover = false; } - } else { this.hover = false; this.showHover = false; @@ -349,57 +425,49 @@ export class Node { } } + /** + * this function draw a node + */ draw() { - - if (this.type == 2) this.checkHover(); - - let ctx = simulationArea.context; - + // console.log(this.id) + const ctx = simulationArea.context; if (this.clicked) { if (this.prev == 'x') { - drawLine(ctx, this.absX(), this.absY(), simulationArea.mouseX, this.absY(), "black", 3); - drawLine(ctx, simulationArea.mouseX, this.absY(), simulationArea.mouseX, simulationArea.mouseY, "black", 3); + drawLine(ctx, this.absX(), this.absY(), simulationArea.mouseX, this.absY(), 'black', 3); + drawLine(ctx, simulationArea.mouseX, this.absY(), simulationArea.mouseX, simulationArea.mouseY, 'black', 3); } else if (this.prev == 'y') { - drawLine(ctx, this.absX(), this.absY(), this.absX(), simulationArea.mouseY, "black", 3); - drawLine(ctx, this.absX(), simulationArea.mouseY, simulationArea.mouseX, simulationArea.mouseY, "black", 3); + drawLine(ctx, this.absX(), this.absY(), this.absX(), simulationArea.mouseY, 'black', 3); + drawLine(ctx, this.absX(), simulationArea.mouseY, simulationArea.mouseX, simulationArea.mouseY, 'black', 3); + } else if (Math.abs(this.x + this.parent.x - simulationArea.mouseX) > Math.abs(this.y + this.parent.y - simulationArea.mouseY)) { + drawLine(ctx, this.absX(), this.absY(), simulationArea.mouseX, this.absY(), 'black', 3); } else { - if (Math.abs(this.x + this.parent.x - simulationArea.mouseX) > Math.abs(this.y + this.parent.y - simulationArea.mouseY)) { - drawLine(ctx, this.absX(), this.absY(), simulationArea.mouseX, this.absY(), "black", 3); - } else { - drawLine(ctx, this.absX(), this.absY(), this.absX(), simulationArea.mouseY, "black", 3); - } + drawLine(ctx, this.absX(), this.absY(), this.absX(), simulationArea.mouseY, 'black', 3); } } - - var color = "black"; - if (this.bitWidth == 1) color = ["green", "lightgreen"][this.value]; - if (this.value == undefined) color = "red"; - if (this.type == 2) - drawCircle(ctx, this.absX(), this.absY(), 3, color); - else drawCircle(ctx, this.absX(), this.absY(), 3, "green"); - - // if (this.highlighted || simulationArea.lastSelected == this || (this.isHover() && !simulationArea.selected && !simulationArea.shiftDown) || simulationArea.multipleObjectSelections.contains(this)) { - // ctx.strokeStyle = "green"; - // ctx.beginPath(); - // ctx.lineWidth = 3; - // arc(ctx, this.x, this.y, 8, 0, Math.PI * 2, this.parent.x, this.parent.y, "RIGHT"); - // ctx.closePath(); - // ctx.stroke(); - // } + let color = 'black'; + if (this.bitWidth == 1) color = ['green', 'lightgreen'][this.value]; + if (this.value == undefined) color = 'red'; + if (this.type == 2) this.checkHover(); + if (this.type == 2) { drawCircle(ctx, this.absX(), this.absY(), 3, color); } else { drawCircle(ctx, this.absX(), this.absY(), 3, 'green'); } + + if (this.highlighted || simulationArea.lastSelected == this || (this.isHover() && !simulationArea.selected && !simulationArea.shiftDown) || simulationArea.multipleObjectSelections.contains(this)) { + ctx.strokeStyle = 'green'; + ctx.beginPath(); + ctx.lineWidth = 3; + arc(ctx, this.x, this.y, 8, 0, Math.PI * 2, this.parent.x, this.parent.y, 'RIGHT'); + ctx.closePath(); + ctx.stroke(); + } if (this.hover || (simulationArea.lastSelected == this)) { - if (this.showHover || simulationArea.lastSelected == this) { - canvasMessageData = { - x: this.absX(), - y: this.absY() - 15 - } + canvasMessageData.x = this.absX(); + canvasMessageData.y = this.absY() - 15; if (this.type == 2) { - var v = "X"; - if (this.value !== undefined) - v = this.value.toString(16); + let v = 'X'; + if (this.value !== undefined) { v = this.value.toString(16); } if (this.label.length) { - canvasMessageData.string = this.label + " : " + v; + canvasMessageData.string = `${this.label} : ${v}`; } else { canvasMessageData.string = v; } @@ -407,29 +475,34 @@ export class Node { canvasMessageData.string = this.label; } } else { - setTimeout(function() { + setTimeout(() => { if (simulationArea.hover) simulationArea.hover.showHover = true; - let canvasUpdate = true; - renderCanvas(globalScope) + updateCanvasSet(true); + renderCanvas(globalScope); }, 400); } } - } + /** + * checks if a node has been deleted + */ checkDeleted() { if (this.deleted) this.delete(); if (this.connections.length == 0 && this.type == 2) this.delete(); } + /** + * used to update nodes if there is a event like click or hover on the node. + * many booleans are used to check if certain properties are to be updated. + */ update() { - if (embed) return; if (this == simulationArea.hover) simulationArea.hover = undefined; this.hover = this.isHover(); - if (!simulationArea.mouseDown) { + if (createNodeGet()) { if (this.absX() != this.prevx || this.absY() != this.prevy) { // Connect to any node this.prevx = this.absX(); this.prevy = this.absY(); @@ -441,7 +514,7 @@ export class Node { simulationArea.hover = this; } - if (simulationArea.mouseDown && ((this.hover && !simulationArea.selected) || simulationArea.lastSelected == this)) { + if (createNodeGet() && ((this.hover && !simulationArea.selected) || simulationArea.lastSelected == this)) { simulationArea.selected = true; simulationArea.lastSelected = this; this.clicked = true; @@ -450,12 +523,11 @@ export class Node { } if (!this.wasClicked && this.clicked) { - this.wasClicked = true; this.prev = 'a'; if (this.type == 2) { if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) { - for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) { + for (let i = 0; i < simulationArea.multipleObjectSelections.length; i++) { simulationArea.multipleObjectSelections[i].startDragging(); } } @@ -471,11 +543,9 @@ export class Node { simulationArea.lastSelected = this; } } - } else if (this.wasClicked && this.clicked) { - if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) { - for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) { + for (let i = 0; i < simulationArea.multipleObjectSelections.length; i++) { simulationArea.multipleObjectSelections[i].drag(); } } @@ -484,7 +554,7 @@ export class Node { this.y = simulationArea.mouseY - this.parent.y; this.prev = 'a'; return; - } else if (this.connections.length == 1 && this.connections[0].absY() == simulationArea.mouseY && this.absY() == simulationArea.mouseY) { + } if (this.connections.length == 1 && this.connections[0].absY() == simulationArea.mouseY && this.absY() == simulationArea.mouseY) { this.x = simulationArea.mouseX - this.parent.x; this.prev = 'a'; return; @@ -509,7 +579,6 @@ export class Node { } else if (this.prev == 'y' && this.absX() == simulationArea.mouseX) { this.prev = 'a'; } - } else if (this.wasClicked && !this.clicked) { this.wasClicked = false; @@ -517,8 +586,10 @@ export class Node { return; // no new node situation } - var x1, y1, x2, y2, flag = 0; - var n1, n2; + let x1; let y1; let x2; let y2; var + flag = 0; + let n1; var + n2; // (x,y) present node, (x1,y1) node 1 , (x2,y2) node 2 // n1 - node 1, n2 - node 2 @@ -527,11 +598,10 @@ export class Node { // flag = 1 - node 1 and node 2 x2 = simulationArea.mouseX; y2 = simulationArea.mouseY; - let x = this.absX(); - let y = this.absY(); + const x = this.absX(); + const y = this.absY(); if (x != x2 && y != y2) { - // Rare Exception Cases if (this.prev == 'a' && distance(simulationArea.mouseX, simulationArea.mouseY, this.absX(), this.absY()) >= 10) { if (Math.abs(this.x + this.parent.x - simulationArea.mouseX) > Math.abs(this.y + this.parent.y - simulationArea.mouseY)) { @@ -547,22 +617,22 @@ export class Node { y1 = y; } else if (this.prev == 'y') { y1 = y2; - x1 = x + x1 = x; } } if (flag == 1) { - for (var i = 0; i < this.parent.scope.allNodes.length; i++) { + for (let i = 0; i < this.parent.scope.allNodes.length; i++) { if (x1 == this.parent.scope.allNodes[i].absX() && y1 == this.parent.scope.allNodes[i].absY()) { n1 = this.parent.scope.allNodes[i]; - + stopWireSet(true); break; } } if (n1 == undefined) { n1 = new Node(x1, y1, 2, this.scope.root); - for (var i = 0; i < this.parent.scope.wires.length; i++) { + for (let i = 0; i < this.parent.scope.wires.length; i++) { if (this.parent.scope.wires[i].checkConvergence(n1)) { this.parent.scope.wires[i].converge(n1); break; @@ -572,16 +642,18 @@ export class Node { this.connect(n1); } - for (var i = 0; i < this.parent.scope.allNodes.length; i++) { + for (let i = 0; i < this.parent.scope.allNodes.length; i++) { if (x2 == this.parent.scope.allNodes[i].absX() && y2 == this.parent.scope.allNodes[i].absY()) { n2 = this.parent.scope.allNodes[i]; + stopWireSet(true); + createNodeSet(false); break; } } if (n2 == undefined) { n2 = new Node(x2, y2, 2, this.scope.root); - for (var i = 0; i < this.parent.scope.wires.length; i++) { + for (let i = 0; i < this.parent.scope.wires.length; i++) { if (this.parent.scope.wires[i].checkConvergence(n2)) { this.parent.scope.wires[i].converge(n2); break; @@ -591,10 +663,9 @@ export class Node { if (flag == 0) this.connect(n2); else n1.connect(n2); if (simulationArea.lastSelected == this) simulationArea.lastSelected = n2; - } - if (this.type == 2 && simulationArea.mouseDown == false) { + if (this.type == 2 && !createNodeGet()) { if (this.connections.length == 2) { if ((this.connections[0].absX() == this.connections[1].absX()) || (this.connections[0].absY() == this.connections[1].absY())) { this.connections[0].connect(this.connections[1]); @@ -602,11 +673,13 @@ export class Node { } } else if (this.connections.length == 0) this.delete(); } - } + /** + * function delete a node + */ delete() { - updateSimulation = true; + updateSimulationSet(true); this.deleted = true; this.parent.scope.allNodes.clean(this); this.parent.scope.nodes.clean(this); @@ -614,12 +687,12 @@ export class Node { this.parent.scope.root.nodeList.clean(this); // Hope this works! - Can cause bugs if (simulationArea.lastSelected == this) simulationArea.lastSelected = undefined; - for (var i = 0; i < this.connections.length; i++) { + for (let i = 0; i < this.connections.length; i++) { this.connections[i].connections.clean(this); this.connections[i].checkDeleted(); } - wireToBeChecked = true; - forceResetNodes = true; + wireToBeCheckedSet(1); + forceResetNodesSet(true); scheduleUpdate(); } @@ -631,17 +704,21 @@ export class Node { return this.absX() == simulationArea.mouseX && this.absY() == simulationArea.mouseY; } + /** + * if input nodde: it resolves the parent + * else: it adds all the nodes onto the stack + * and they are processed to generate verilog + */ nodeConnect() { + const x = this.absX(); + const y = this.absY(); + let n; - var x = this.absX(); - var y = this.absY(); - var n; - - for (var i = 0; i < this.parent.scope.allNodes.length; i++) { + for (let i = 0; i < this.parent.scope.allNodes.length; i++) { if (this != this.parent.scope.allNodes[i] && x == this.parent.scope.allNodes[i].absX() && y == this.parent.scope.allNodes[i].absY()) { n = this.parent.scope.allNodes[i]; if (this.type == 2) { - for (var j = 0; j < this.connections.length; j++) { + for (let j = 0; j < this.connections.length; j++) { n.connect(this.connections[j]); } this.delete(); @@ -654,9 +731,9 @@ export class Node { } if (n == undefined) { - for (var i = 0; i < this.parent.scope.wires.length; i++) { + for (let i = 0; i < this.parent.scope.wires.length; i++) { if (this.parent.scope.wires[i].checkConvergence(this)) { - var n = this; + let n = this; if (this.type != 2) { n = new Node(this.absX(), this.absY(), 2, this.scope.root); this.connect(n); @@ -666,208 +743,30 @@ export class Node { } } } - } processVerilog() { - if (this.type == NODE_INPUT) { - if (this.parent.isVerilogResolvable()) - this.scope.stack.push(this.parent); + if (this.parent.isVerilogResolvable()) { this.scope.stack.push(this.parent); } } - for (var i = 0; i < this.connections.length; i++) { + for (let i = 0; i < this.connections.length; i++) { if (this.connections[i].verilogLabel != this.verilogLabel) { this.connections[i].verilogLabel = this.verilogLabel; this.scope.stack.push(this.connections[i]); } } } - - - - // Kept for archival purposes - // function oldNodeUpdate() { - // - // if (!this.clicked && !simulationArea.mouseDown) { - // var px = this.prevx; - // var py = this.prevy; - // this.prevx = this.absX(); - // this.prevy = this.absY(); - // if (this.absX() != px || this.absY() != py) { - // updated = true; - // this.nodeConnect(); - // return updated; - // } - // } - // - // var updated = false; - // if (!simulationArea.mouseDown) this.hover = false; - // if ((this.clicked || !simulationArea.hover) && this.isHover()) { - // this.hover = true; - // simulationArea.hover = this; - // } else if (!simulationArea.mouseDown && this.hover && this.isHover() == false) { - // if (this.hover) simulationArea.hover = undefined; - // this.hover = false; - // } - // - // if (simulationArea.mouseDown && (this.clicked)) { - // - // if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) { - // for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) { - // simulationArea.multipleObjectSelections[i].drag(); - // } - // } - // - // if (this.type == 2) { - // if (this.absX() == simulationArea.mouseX && this.absY() == simulationArea.mouseY) { - // updated = false; - // this.prev = 'a'; - // } else if (this.connections.length == 1 && this.connections[0].absX() == simulationArea.mouseX && this.absX() == simulationArea.mouseX) { - // this.y = simulationArea.mouseY - this.parent.y; - // this.prev = 'a'; - // updated = true; - // } else if (this.connections.length == 1 && this.connections[0].absY() == simulationArea.mouseY && this.absY() == simulationArea.mouseY) { - // this.x = simulationArea.mouseX - this.parent.x; - // this.prev = 'a'; - // updated = true; - // } - // if (this.connections.length == 1 && this.connections[0].absX() == this.absX() && this.connections[0].absY() == this.absY()) { - // this.connections[0].clicked = true; - // this.connections[0].wasClicked = true; - // this.delete(); - // updated = true; - // } - // } - // if (this.prev == 'a' && distance(simulationArea.mouseX, simulationArea.mouseY, this.absX(), this.absY()) >= 10) { - // if (Math.abs(this.x + this.parent.x - simulationArea.mouseX) > Math.abs(this.y + this.parent.y - simulationArea.mouseY)) { - // this.prev = 'x'; - // } else { - // this.prev = 'y'; - // } - // } - // } else if (simulationArea.mouseDown && !simulationArea.selected) { - // simulationArea.selected = this.clicked = this.hover; - // updated |= this.clicked; - // this.prev = 'a'; - // } else if (!simulationArea.mouseDown) { - // if (this.clicked) simulationArea.selected = false; - // this.clicked = false; - // this.count = 0; - // } - // - // if (this.clicked && !this.wasClicked) { - // this.wasClicked = true; - // if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) { - // for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) { - // simulationArea.multipleObjectSelections[i].startDragging(); - // } - // } - // - // if (this.type == 2) { - // if (simulationArea.shiftDown) { - // simulationArea.lastSelected = undefined; - // if (simulationArea.multipleObjectSelections.contains(this)) { - // simulationArea.multipleObjectSelections.clean(this); - // } else { - // simulationArea.multipleObjectSelections.push(this); - // } - // } else { - // simulationArea.lastSelected = this; - // } - // } - // } - // - // if (this.wasClicked && !this.clicked) { - // this.wasClicked = false; - // - // if (simulationArea.mouseX == this.absX() && simulationArea.mouseY == this.absY()) { - // this.nodeConnect(); - // return updated; - // } - // - // var n, n1; - // var x, y, x1, y1, flag = -1; - // x1 = simulationArea.mouseX; - // y1 = simulationArea.mouseY; - // if (this.prev == 'x') { - // x = x1; - // y = this.absY(); - // flag = 0; - // } else if (this.prev == 'y') { - // y = y1; - // x = this.absX(); - // flag = 1; - // } - // if (this.type == 'a') return; // this should never happen!! - // - // for (var i = 0; i < this.parent.scope.allNodes.length; i++) { - // if (x == this.parent.scope.allNodes[i].absX() && y == this.parent.scope.allNodes[i].absY()) { - // n = this.parent.scope.allNodes[i]; - // this.connect(n); - // break; - // } - // } - // - // if (n == undefined) { - // n = new Node(x, y, 2, this.scope.root); - // this.connect(n); - // for (var i = 0; i < this.parent.scope.wires.length; i++) { - // if (this.parent.scope.wires[i].checkConvergence(n)) { - // this.parent.scope.wires[i].converge(n); - // } - // } - // } - // this.prev = 'a'; - // - // if (flag == 0 && (this.y + this.parent.y - simulationArea.mouseY) != 0) { - // y = y1; - // flag = 2; - // } else if ((this.x + this.parent.x - simulationArea.mouseX) != 0 && flag == 1) { - // x = x1; - // flag = 2; - // } - // if (flag == 2) { - // for (var i = 0; i < this.parent.scope.allNodes.length; i++) { - // if (x == this.parent.scope.allNodes[i].absX() && y == this.parent.scope.allNodes[i].absY()) { - // n1 = this.parent.scope.allNodes[i]; - // n.connect(n1); - // break; - // } - // } - // if (n1 == undefined) { - // n1 = new Node(x, y, 2, this.scope.root); - // n.connect(n1); - // for (var i = 0; i < this.parent.scope.wires.length; i++) { - // if (this.parent.scope.wires[i].checkConvergence(n1)) { - // this.parent.scope.wires[i].converge(n1); - // } - // } - // } - // - // } - // updated = true; - // - // if (simulationArea.lastSelected == this) simulationArea.lastSelected = undefined; - // } - // - // if (this.type == 2) { - // if (this.connections.length == 2 && simulationArea.mouseDown == false) { - // if ((this.connections[0].absX() == this.connections[1].absX()) || (this.connections[0].absY() == this.connections[1].absY())) { - // this.connections[0].connect(this.connections[1]); - // this.delete(); - // } - // } else if (this.connections.length == 0) this.delete(); - // } - // - // // if (this.clicked && this.type == 2 && simulationArea.lastSelected == undefined) simulationArea.lastSelected = this; - // return updated; - // - // - // - // } } +/** + * delay in simulation of the node. + * @category node + */ Node.prototype.propagationDelay = 0; -Node.prototype.subcircuitOverride = false; + +/** + * backward comaptibilty? + * @category node + */ Node.prototype.cleanDelete = Node.prototype.delete; diff --git a/src/plotArea.js b/src/plotArea.js index 304c574..75ce61b 100644 --- a/src/plotArea.js +++ b/src/plotArea.js @@ -1,43 +1,58 @@ +/** + * @module plotArea + * @category plotArea + */ +/** + * function to add the plotting div + * @memberof module:plotArea + * @category plotArea + */ function addPlot() { plotArea.ox = 0; plotArea.oy = 0; plotArea.count = 0; - plotArea.unit = 1000;//parseInt(prompt("Enter unit of time(in milli seconds)")); + plotArea.unit = 1000;// parseInt(prompt("Enter unit of time(in milli seconds)")); plotArea.specificTimeX = 0; - } - +/** + * Used as a stopwatch to + * record time side of the plot + * @category plotArea + */ class StopWatch { constructor() { this.StartMilliseconds = 0; this.ElapsedMilliseconds = 0; } + /** + * @memberof StopWatch + * used to start the stopwatch + */ Start() { this.StartMilliseconds = new Date().getTime(); } + /** + * @memberof StopWatch + * used to get time elapsed. + */ Stop() { this.ElapsedMilliseconds = new Date().getTime() - this.StartMilliseconds; } } -function startPlot() { - plotArea.stopWatch.Start(); - for (var i = 0; i < globalScope.Flag.length; i++) { - globalScope.Flag[i].plotValues = [[0, globalScope.Flag[i].inp1.value]]; - globalScope.Flag[i].cachedIndex = 0; - } - // play(); - plotArea.scroll = 1; - addPlot(); -} -export var plotArea = { + +/** + * @type {Object} plotArea + * @category plotArea + */ +const plotArea = { ox: 0, oy: 0, unit: 0, - c: document.getElementById("plotArea"), - + c: document.getElementById('plotArea'), + count: 0, specificTimeX: 0, scale: 1, @@ -45,30 +60,30 @@ export var plotArea = { startTime: new Date(), endTime: new Date(), scroll: 1, - setup: function () { - this.c = document.getElementById("plotArea") - this.stopWatch = new StopWatch() + setup() { + this.c = document.getElementById('plotArea'); + this.stopWatch = new StopWatch(); this.stopWatch.Start(); - this.ctx = this.c.getContext("2d"); + this.ctx = this.c.getContext('2d'); startPlot(); - this.timeOutPlot = setInterval(function () { + this.timeOutPlot = setInterval(() => { plotArea.plot(); }, 100); }, - plot: function () { + plot() { if (globalScope.Flag.length == 0) { this.c.width = this.c.height = 0; return; } this.stopWatch.Stop(); - var time = this.stopWatch.ElapsedMilliseconds; - this.c.width = document.getElementById("simulationArea").clientWidth; //innerWidth; + const time = this.stopWatch.ElapsedMilliseconds; + this.c.width = document.getElementById('simulationArea').clientWidth; // innerWidth; this.c.height = globalScope.Flag.length * 30 + 40; // if(document.getElementById("plot").style.height!=this.c.height+"px"){ - document.getElementById("plot").style.height = Math.min(this.c.height, 400); - document.getElementById("plot").style.width = this.c.width; + document.getElementById('plot').style.height = Math.min(this.c.height, 400); + document.getElementById('plot').style.width = this.c.width; // } // else if(document.getElementById("plot").style.width!=this.c.width+"px"){ // document.getElementById("plot").style.height = this.c.height ; @@ -77,28 +92,25 @@ export var plotArea = { if (this.scroll) { this.ox = Math.max(0, (time / this.unit) * this.pixel - this.c.width + 70); - } - else if (!plotArea.mouseDown) { + } else if (!plotArea.mouseDown) { this.ox -= plotArea.scrollAcc; plotArea.scrollAcc *= 0.95; if (this.ox < 0) plotArea.scrollAcc = -0.2 + 0.2 * this.ox; if (Math.abs(this.ox) < 0.5) this.ox = 0; } - var ctx = this.ctx; + const { ctx } = this; this.clear(ctx); ctx.fillStyle = 'black'; ctx.fillRect(0, 0, this.c.width, this.c.height); - var unit = (this.pixel / (plotArea.unit * plotArea.scale)) + const unit = (this.pixel / (plotArea.unit * plotArea.scale)); ctx.strokeStyle = 'cyan'; ctx.lineWidth = 2; - for (var i = 0; i < globalScope.Flag.length; i++) { - - var arr = globalScope.Flag[i].plotValues; + for (let i = 0; i < globalScope.Flag.length; i++) { + const arr = globalScope.Flag[i].plotValues; - var j = 0; - // var start=arr[j][0]; - if (globalScope.Flag[i].cachedIndex) - j = globalScope.Flag[i].cachedIndex; + let j = 0; + // let start=arr[j][0]; + if (globalScope.Flag[i].cachedIndex) { j = globalScope.Flag[i].cachedIndex; } while (j < arr.length && 80 + (arr[j][0] * unit) - plotArea.ox < 0) { @@ -115,31 +127,22 @@ export var plotArea = { globalScope.Flag[i].cachedIndex = j; for (; j < arr.length; j++) { - var start = arr[j][0]; - - if (j + 1 == arr.length) - var end = time; - else - var end = arr[j + 1][0]; - + const start = arr[j][0]; + let end; + if (j + 1 == arr.length) { end = time; } else { end = arr[j + 1][0]; } + let yOff; if (start <= time) { - - - if (globalScope.Flag[i].bitWidth == 1) { - if (arr[j][1] !== undefined) { - - if (ctx.strokeStyle == "#000000") { - ctx.stroke() + if (ctx.strokeStyle == '#000000') { + ctx.stroke(); ctx.beginPath(); ctx.lineWidth = 2; // ctx.moveTo(80+(start*unit)-plotArea.ox,2*(30+i*15-yOff)-plotArea.oy) - ctx.strokeStyle = "cyan"; + ctx.strokeStyle = 'cyan'; } - var yOff = 5 * arr[j][1]; - } - else { + yOff = 5 * arr[j][1]; + } else { ctx.stroke(); ctx.beginPath(); // ctx.moveTo(80+(start*unit)-plotArea.ox,2*(30+i*15-yOff)-plotArea.oy); @@ -149,8 +152,7 @@ export var plotArea = { } ctx.lineTo(80 + (start * unit) - plotArea.ox, 2 * (30 + i * 15 - yOff) - plotArea.oy); ctx.lineTo(80 + (end * unit) - plotArea.ox, 2 * (30 + i * 15 - yOff) - plotArea.oy); - } - else { + } else { ctx.beginPath(); ctx.moveTo(80 + (end * unit) - plotArea.ox, 55 + 30 * i - plotArea.oy); ctx.lineTo(80 + (end * unit) - 5 - plotArea.ox, 2 * (25 + i * 15) - plotArea.oy); @@ -160,18 +162,17 @@ export var plotArea = { ctx.lineTo(80 + (end * unit) - 5 - plotArea.ox, 2 * (30 + i * 15) - plotArea.oy); ctx.lineTo(80 + (end * unit) - plotArea.ox, 55 + 30 * i - plotArea.oy); mid = 80 + ((end + start) / Math.round(plotArea.unit * plotArea.scale)) * this.pixel / 2; - ctx.font = "12px Georgia"; + ctx.font = '12px Georgia'; ctx.fillStyle = 'white'; if ((start * unit) + 10 - plotArea.ox <= 0 && (end * unit) + 10 - plotArea.ox >= 0) { mid = 80 + ((end - 3000) / Math.round(plotArea.unit * plotArea.scale)) * this.pixel; } - ctx.fillText(arr[j][1].toString() || "x", mid - plotArea.ox, 57 + 30 * i - plotArea.oy); + ctx.fillText(arr[j][1].toString() || 'x', mid - plotArea.ox, 57 + 30 * i - plotArea.oy); ctx.stroke(); } - } - else { + } else { break; } @@ -180,36 +181,35 @@ export var plotArea = { ctx.stroke(); ctx.beginPath(); - } // 2 rectangles showing the time and labels ctx.fillStyle = '#eee'; ctx.fillRect(0, 0, this.c.width, 30); - ctx.font = "14px Georgia"; + ctx.font = '14px Georgia'; ctx.fillStyle = 'black'; - for (var i = 1; i * Math.round(plotArea.unit * plotArea.scale) <= time + Math.round(plotArea.unit * plotArea.scale); i++) { - ctx.fillText(Math.round(plotArea.unit * plotArea.scale) * i / 1000 + "s", 48 + this.pixel * i - plotArea.ox, 20); + for (let i = 1; i * Math.round(plotArea.unit * plotArea.scale) <= time + Math.round(plotArea.unit * plotArea.scale); i++) { + ctx.fillText(`${Math.round(plotArea.unit * plotArea.scale) * i / 1000}s`, 48 + this.pixel * i - plotArea.ox, 20); } ctx.fillStyle = '#eee'; ctx.fillRect(0, 0, 75, this.c.height); - ctx.font = "15px Georgia"; + ctx.font = '15px Georgia'; ctx.fillStyle = 'black'; - for (var i = 0; i < globalScope.Flag.length; i++) { + for (let i = 0; i < globalScope.Flag.length; i++) { ctx.fillText(globalScope.Flag[i].identifier, 5, 2 * (25 + i * 15) - plotArea.oy); ctx.fillRect(0, 2 * (13 + i * 15) + 4 - plotArea.oy, 75, 3); } ctx.fillStyle = '#eee'; ctx.fillRect(0, 0, 75, 30); ctx.fillStyle = 'black'; - ctx.font = "16px Georgia"; - ctx.fillText("Time", 10, 20); + ctx.font = '16px Georgia'; + ctx.fillText('Time', 10, 20); ctx.strokeStyle = 'black'; ctx.moveTo(0, 25); ctx.lineTo(75, 25); // for yellow line to show specific time - var specificTime = (plotArea.specificTimeX + plotArea.ox - 80) * Math.round(plotArea.unit * plotArea.scale) / (this.pixel);; + const specificTime = (plotArea.specificTimeX + plotArea.ox - 80) * Math.round(plotArea.unit * plotArea.scale) / (this.pixel); // ctx.strokeStyle = 'white'; // ctx.moveTo(plotArea.specificTimeX,0); // ctx.lineTo(plotArea.specificTimeX,plotArea.c.height); @@ -219,9 +219,9 @@ export var plotArea = { ctx.fillRect(plotArea.specificTimeX - 35, 0, 70, 30); ctx.fillStyle = 'red'; ctx.fillRect(plotArea.specificTimeX - 30, 2, 60, 26); - ctx.font = "12px Georgia"; + ctx.font = '12px Georgia'; ctx.fillStyle = 'black'; - ctx.fillText(Math.round(specificTime) + "ms", plotArea.specificTimeX - 25, 20); + ctx.fillText(`${Math.round(specificTime)}ms`, plotArea.specificTimeX - 25, 20); } // borders ctx.strokeStyle = 'black'; @@ -244,48 +244,59 @@ export var plotArea = { // ctx.fillRect(0, 27, this.c.width, 3); ctx.stroke(); }, - clear: function (ctx) { + clear(ctx) { ctx.clearRect(0, 0, plotArea.c.width, plotArea.c.height); // clearInterval(timeOutPlot); - } + }, +}; +export default plotArea; +if (document.getElementById('plotArea') !== null) { + /** + * Event listeners for the Plot + */ + document.getElementById('plotArea').addEventListener('mousedown', (e) => { + const rect = plotArea.c.getBoundingClientRect(); + const x = e.clientX - rect.left; + plotArea.scrollAcc = 0; + if (e.shiftKey) { + plotArea.specificTimeX = x; + } else { + plotArea.scroll = 0; + plotArea.mouseDown = true; + + plotArea.prevX = x; + console.log('HIT'); + } + }); + document.getElementById('plotArea').addEventListener('mouseup', (e) => { + plotArea.mouseDown = false; + }); + + document.getElementById('plotArea').addEventListener('mousemove', (e) => { + const rect = plotArea.c.getBoundingClientRect(); + const x = e.clientX - rect.left; + if (!e.shiftKey && plotArea.mouseDown) { + plotArea.ox -= x - plotArea.prevX; + plotArea.scrollAcc = x - plotArea.prevX; + plotArea.prevX = x; + // plotArea.ox=Math.max(0,plotArea.ox) + } else { + plotArea.mouseDown = false; + } + }); +} +/** + * sets plot values of all flags and it add(s)Plot(). + * @category plotArea + */ +function startPlot() { + plotArea.stopWatch.Start(); + for (let i = 0; i < globalScope.Flag.length; i++) { + globalScope.Flag[i].plotValues = [[0, globalScope.Flag[i].inp1.value]]; + globalScope.Flag[i].cachedIndex = 0; + } + // play(); + plotArea.scroll = 1; + addPlot(); } - -// document.getElementById("plotArea").addEventListener('mousedown', function (e) { - -// var rect = plotArea.c.getBoundingClientRect(); -// var x = e.clientX - rect.left; -// plotArea.scrollAcc = 0; -// if (e.shiftKey) { -// plotArea.specificTimeX = x; -// } -// else { -// plotArea.scroll = 0; -// plotArea.mouseDown = true; - -// plotArea.prevX = x; -// console.log("HIT"); -// } -// }); -// document.getElementById("plotArea").addEventListener('mouseup', function (e) { - -// plotArea.mouseDown = false; -// }); - -// document.getElementById("plotArea").addEventListener('mousemove', function (e) { - -// var rect = plotArea.c.getBoundingClientRect(); -// var x = e.clientX - rect.left; -// if (!e.shiftKey && plotArea.mouseDown) { -// plotArea.ox -= x - plotArea.prevX; -// plotArea.scrollAcc = x - plotArea.prevX; -// plotArea.prevX = x; -// // plotArea.ox=Math.max(0,plotArea.ox) -// } -// else { -// plotArea.mouseDown = false; - - - -// } -// }); diff --git a/src/quinMcCluskey.js b/src/quinMcCluskey.js new file mode 100644 index 0000000..c364463 --- /dev/null +++ b/src/quinMcCluskey.js @@ -0,0 +1,218 @@ +// Algorithm used for Combinational Analysis + +export default function BooleanMinimize(numVarsArg, minTermsArg, dontCaresArg = []) { + let __result; + + Object.defineProperties( + this, { + minTerms: { + value: minTermsArg, + enumerable: false, + writable: false, + configurable: true, + }, + + dontCares: { + value: dontCaresArg, + enumerable: false, + writable: false, + configurable: true, + }, + + numVars: { + value: numVarsArg, + enumerable: false, + writable: false, + configurable: true, + }, + + result: { + enumerable: true, + configurable: true, + get() { + if (__result === undefined) { + __result = BooleanMinimize.prototype.solve.call(this); + } + + return __result; + }, + set() { + throw new Error('result cannot be assigned a value'); + }, + }, + }, + ); +} + +BooleanMinimize.prototype.solve = function () { + function dec_to_binary_string(n) { + let str = n.toString(2); + + while (str.length != this.numVars) { + str = `0${str}`; + } + + return str; + } + + function num_set_bits(s) { + let ans = 0; + for (let i = 0; i < s.length; ++i) { if (s[i] === '1') ans++; } + return ans; + } + + function get_prime_implicants(allTerms) { + const table = []; + const primeImplicants = new Set(); + let reduced; + + while (1) { + for (let i = 0; i <= this.numVars; ++i) table[i] = new Set(); + for (let i = 0; i < allTerms.length; ++i) table[num_set_bits(allTerms[i])].add(allTerms[i]); + + allTerms = []; + reduced = new Set(); + + for (let i = 0; i < table.length - 1; ++i) { + for (const str1 of table[i]) { + for (const str2 of table[i + 1]) { + let diff = -1; + + for (let j = 0; j < this.numVars; ++j) { + if (str1[j] != str2[j]) { + if (diff === -1) { + diff = j; + } else { + diff = -1; + break; + } + } + } + + if (diff !== -1) { + allTerms.push(`${str1.slice(0, diff)}-${str1.slice(diff + 1)}`); + reduced.add(str1); + reduced.add(str2); + } + } + } + } + + for (const t of table) { + for (const str of t) { + if (!(reduced.has(str))) primeImplicants.add(str); + } + } + + if (!reduced.size) break; + } + + return primeImplicants; + } + + function get_essential_prime_implicants(primeImplicants, minTerms) { + const table = []; + let column; + + function check_if_similar(minTerm, primeImplicant) { + for (let i = 0; i < primeImplicant.length; ++i) { + if (primeImplicant[i] !== '-' && (minTerm[i] !== primeImplicant[i])) return false; + } + + return true; + } + + function get_complexity(terms) { + let complexity = terms.length; + + for (const t of terms) { + for (let i = 0; i < t.length; ++i) { + if (t[i] !== '-') { + complexity++; + if (t[i] === '0') complexity++; + } + } + } + + return complexity; + } + + function isSubset(sub, sup) { + for (const i of sub) { + if (!(sup.has(i))) return false; + } + + return true; + } + + for (const m of minTerms) { + column = []; + + for (let i = 0; i < primeImplicants.length; ++i) { + if (check_if_similar(m, primeImplicants[i])) { + column.push(i); + } + } + + table.push(column); + } + + let possibleSets = []; + let tempSets; + + for (const i of table[0]) { + possibleSets.push(new Set([i])); + } + + for (let i = 1; i < table.length; ++i) { + tempSets = []; + for (const s of possibleSets) { + for (const p of table[i]) { + const x = new Set(s); + x.add(p); + let append = true; + + for (let j = tempSets.length - 1; j >= 0; --j) { + if (isSubset(x, tempSets[j])) { + tempSets.splice(j, 1); + } else { + append = false; + } + } + + if (append) { + tempSets.push(x); + } + } + + possibleSets = tempSets; + } + } + + let essentialImplicants; let + minComplexity = 1e9; + + for (const s of possibleSets) { + const p = []; + for (const i of s) { + p.push(primeImplicants[i]); + } + const comp = get_complexity(p); + if (comp < minComplexity) { + essentialImplicants = p; + minComplexity = comp; + } + } + + return essentialImplicants; + } + + const minTerms = this.minTerms.map(dec_to_binary_string.bind(this)); + const dontCares = this.dontCares.map(dec_to_binary_string.bind(this)); + + return get_essential_prime_implicants.call( + this, + Array.from(get_prime_implicants.call(this, minTerms.concat(dontCares))), + minTerms, + ); +}; diff --git a/src/restrictedElementDiv.js b/src/restrictedElementDiv.js index 4995e56..6858e3c 100644 --- a/src/restrictedElementDiv.js +++ b/src/restrictedElementDiv.js @@ -1,21 +1,21 @@ -function updateRestrictedElementsList() { +export function updateRestrictedElementsList() { if (restrictedElements.length === 0) return; - const restrictedCircuitElementsUsed = globalScope.restrictedCircuitElementsUsed; - let restrictedStr = ""; + const { restrictedCircuitElementsUsed } = globalScope; + let restrictedStr = ''; restrictedCircuitElementsUsed.forEach((element) => { restrictedStr += `${element}, `; }); - if (restrictedStr === "") { - restrictedStr = "None"; + if (restrictedStr === '') { + restrictedStr = 'None'; } else { restrictedStr = restrictedStr.slice(0, -2); } - $("#restrictedElementsDiv--list").html(restrictedStr); + $('#restrictedElementsDiv--list').html(restrictedStr); } @@ -23,7 +23,7 @@ export function updateRestrictedElementsInScope(scope = globalScope) { // Do nothing if no restricted elements if (restrictedElements.length === 0) return; - let restrictedElementsUsed = []; + const restrictedElementsUsed = []; restrictedElements.forEach((element) => { if (scope[element].length > 0) { restrictedElementsUsed.push(element); @@ -34,3 +34,13 @@ export function updateRestrictedElementsInScope(scope = globalScope) { updateRestrictedElementsList(); } +export function showRestricted() { + $('#restrictedDiv').removeClass('display--none'); + // Show no help text for restricted elements + $('#Help').removeClass('show'); + $('#restrictedDiv').html('The element has been restricted by mentor. Usage might lead to deduction in marks'); +} + +export function hideRestricted() { + $('#restrictedDiv').addClass('display--none'); +} diff --git a/src/sequential.js b/src/sequential.js new file mode 100644 index 0000000..fa59d29 --- /dev/null +++ b/src/sequential.js @@ -0,0 +1,24 @@ +import { scheduleUpdate, play, updateCanvasSet } from './engine'; +import simulationArea from './simulationArea'; + +/** + * a global function as a helper for simulationArea.changeClockEnable + * @category sequential + */ +export function changeClockEnable(val) { + simulationArea.clockEnabled = val; +} + +/** + * WIP function defined and used + * @param {number} n + * @category sequential + */ +export function runTest(n = 10) { + const t = new Date().getTime(); + for (let i = 0; i < n; i++) { clockTick(); } + // console.log((new Date().getTime()-t)/n); + updateCanvasSet(true); + play(); + scheduleUpdate(); +} diff --git a/src/sequential/Clock.js b/src/sequential/Clock.js new file mode 100644 index 0000000..d75edf0 --- /dev/null +++ b/src/sequential/Clock.js @@ -0,0 +1,83 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { correctWidth, lineTo, moveTo } from '../canvasApi'; +/** + * @class + * Clock + * Clock + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * @category sequential + */ +export default class Clock extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT') { + super(x, y, scope, dir, 1); + /* + this.scope['Clock'].push(this); + */ + this.fixedBitWidth = true; + this.output1 = new Node(10, 0, 1, this, 1); + this.state = 0; + this.output1.value = this.state; + this.wasClicked = false; + this.interval = null; + } + + customSave() { + const data = { + nodes: { + output1: findNode(this.output1), + }, + constructorParamaters: [this.direction], + }; + return data; + } + + resolve() { + this.output1.value = this.state; + simulationArea.simulationQueue.add(this.output1); + } + + toggleState() { // toggleState + this.state = (this.state + 1) % 2; + this.output1.value = this.state; + } + + customDraw() { + const ctx = simulationArea.context; + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + + ctx.beginPath(); + ctx.strokeStyle = ['DarkGreen', 'Lime'][this.state]; + ctx.lineWidth = correctWidth(2); + if (this.state == 0) { + moveTo(ctx, -6, 0, xx, yy, 'RIGHT'); + lineTo(ctx, -6, 5, xx, yy, 'RIGHT'); + lineTo(ctx, 0, 5, xx, yy, 'RIGHT'); + lineTo(ctx, 0, -5, xx, yy, 'RIGHT'); + lineTo(ctx, 6, -5, xx, yy, 'RIGHT'); + lineTo(ctx, 6, 0, xx, yy, 'RIGHT'); + } else { + moveTo(ctx, -6, 0, xx, yy, 'RIGHT'); + lineTo(ctx, -6, -5, xx, yy, 'RIGHT'); + lineTo(ctx, 0, -5, xx, yy, 'RIGHT'); + lineTo(ctx, 0, 5, xx, yy, 'RIGHT'); + lineTo(ctx, 6, 5, xx, yy, 'RIGHT'); + lineTo(ctx, 6, 0, xx, yy, 'RIGHT'); + } + ctx.stroke(); + } +} + +Clock.prototype.tooltipText = 'Clock'; + +Clock.prototype.click = Clock.prototype.toggleState; +Clock.prototype.objectType = 'Clock'; diff --git a/src/sequential/DflipFlop.js b/src/sequential/DflipFlop.js new file mode 100644 index 0000000..0123998 --- /dev/null +++ b/src/sequential/DflipFlop.js @@ -0,0 +1,142 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText, +} from '../canvasApi'; +/** + * @class + * DflipFlop + * D flip flop has 5 input nodes: + * clock, data input, preset, reset ,enable. + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * @category sequential + */ +export default class DflipFlop extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* + this.scope['DflipFlop'].push(this); + */ + this.directionFixed = true; + this.setDimensions(20, 20); + this.rectangleObject = true; + this.clockInp = new Node(-20, +10, 0, this, 1, 'Clock'); + this.dInp = new Node(-20, -10, 0, this, this.bitWidth, 'D'); + this.qOutput = new Node(20, -10, 1, this, this.bitWidth, 'Q'); + this.qInvOutput = new Node(20, 10, 1, this, this.bitWidth, 'Q Inverse'); + this.reset = new Node(10, 20, 0, this, 1, 'Asynchronous Reset'); + this.preset = new Node(0, 20, 0, this, this.bitWidth, 'Preset'); + this.en = new Node(-10, 20, 0, this, 1, 'Enable'); + this.masterState = 0; + this.slaveState = 0; + this.prevClockState = 0; + + this.wasClicked = false; + } + + /** + * WIP always resolvable? + */ + isResolvable() { + return true; + // if (this.reset.value == 1) return true; + // if (this.clockInp.value != undefined && this.dInp.value != undefined) return true; + // return false; + } + + newBitWidth(bitWidth) { + this.bitWidth = bitWidth; + this.dInp.bitWidth = bitWidth; + this.qOutput.bitWidth = bitWidth; + this.qInvOutput.bitWidth = bitWidth; + this.preset.bitWidth = bitWidth; + } + + /** + * @memberof DflipFlop + * On the leading edge of the clock signal (LOW-to-HIGH) the first stage, + * the “master” latches the input condition at D, while the output stage is deactivated. + * On the trailing edge of the clock signal (HIGH-to-LOW) the second “slave” stage is + * now activated, latching on to the output from the first master circuit. + * Then the output stage appears to be triggered on the negative edge of the clock pulse. + * This fuction sets the value for the node qOutput based on the previous state + * and input of the clock. We flip the bits to find qInvOutput + */ + resolve() { + if (this.reset.value == 1) { + this.masterState = this.slaveState = (this.preset.value || 0); + } else if (this.en.value == 0) { + this.prevClockState = this.clockInp.value; + } else if (this.en.value == 1 || this.en.connections.length == 0) { // if(this.en.value==1) // Creating Infinite Loop, WHY ?? + if (this.clockInp.value == this.prevClockState) { + if (this.clockInp.value == 0 && this.dInp.value != undefined) { + this.masterState = this.dInp.value; + } + } else if (this.clockInp.value != undefined) { + if (this.clockInp.value == 1) { + this.slaveState = this.masterState; + } else if (this.clockInp.value == 0 && this.dInp.value != undefined) { + this.masterState = this.dInp.value; + } + this.prevClockState = this.clockInp.value; + } + } + + if (this.qOutput.value != this.slaveState) { + this.qOutput.value = this.slaveState; + this.qInvOutput.value = this.flipBits(this.slaveState); + simulationArea.simulationQueue.add(this.qOutput); + simulationArea.simulationQueue.add(this.qInvOutput); + } + } + + customSave() { + const data = { + nodes: { + clockInp: findNode(this.clockInp), + dInp: findNode(this.dInp), + qOutput: findNode(this.qOutput), + qInvOutput: findNode(this.qInvOutput), + reset: findNode(this.reset), + preset: findNode(this.preset), + en: findNode(this.en), + }, + constructorParamaters: [this.direction, this.bitWidth], + + }; + return data; + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + // rect(ctx, xx - 20, yy - 20, 40, 40); + moveTo(ctx, -20, 5, xx, yy, this.direction); + lineTo(ctx, -15, 10, xx, yy, this.direction); + lineTo(ctx, -20, 15, xx, yy, this.direction); + // if ((this.b.hover&&!simulationArea.shiftDown)|| simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)";ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.font = '20px Georgia'; + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + fillText(ctx, this.slaveState.toString(16), xx, yy + 5); + ctx.fill(); + } +} + +DflipFlop.prototype.tooltipText = 'D FlipFlop ToolTip : Introduces delay in timing circuit.'; +DflipFlop.prototype.helplink = 'https://docs.circuitverse.org/#/Sequential?id=d-flip-flop'; + +DflipFlop.prototype.objectType = 'DflipFlop'; diff --git a/src/sequential/Dlatch.js b/src/sequential/Dlatch.js new file mode 100644 index 0000000..f23ed3d --- /dev/null +++ b/src/sequential/Dlatch.js @@ -0,0 +1,119 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText, +} from '../canvasApi'; +/** + * @class + * Dlatch + * D latch has 2 input nodes: + * clock, data input. + * Difference between this and D - FlipFlop is + * that Flip flop must have a clock. + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * @category sequential + */ +export default class Dlatch extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + /* + this.scope['Dlatch'].push(this); + */ + this.directionFixed = true; + this.setDimensions(20, 20); + this.rectangleObject = true; + this.clockInp = new Node(-20, +10, 0, this, 1, 'Clock'); + this.dInp = new Node(-20, -10, 0, this, this.bitWidth, 'D'); + this.qOutput = new Node(20, -10, 1, this, this.bitWidth, 'Q'); + this.qInvOutput = new Node(20, 10, 1, this, this.bitWidth, 'Q Inverse'); + // this.reset = new Node(10, 20, 0, this, 1, "Asynchronous Reset"); + // this.preset = new Node(0, 20, 0, this, this.bitWidth, "Preset"); + // this.en = new Node(-10, 20, 0, this, 1, "Enable"); + this.state = 0; + this.prevClockState = 0; + this.wasClicked = false; + } + + /** + * Idea: shoould be D FF? + */ + isResolvable() { + if (this.clockInp.value != undefined && this.dInp.value != undefined) return true; + return false; + } + + newBitWidth(bitWidth) { + this.bitWidth = bitWidth; + this.dInp.bitWidth = bitWidth; + this.qOutput.bitWidth = bitWidth; + this.qInvOutput.bitWidth = bitWidth; + // this.preset.bitWidth = bitWidth; + } + + /** + * @memberof Dlatch + * when the clock input is high we update the state + * qOutput is set to the state + */ + resolve() { + if (this.clockInp.value == 1 && this.dInp.value != undefined) { + this.state = this.dInp.value; + } + + if (this.qOutput.value != this.state) { + this.qOutput.value = this.state; + this.qInvOutput.value = this.flipBits(this.state); + simulationArea.simulationQueue.add(this.qOutput); + simulationArea.simulationQueue.add(this.qInvOutput); + } + } + + customSave() { + const data = { + nodes: { + clockInp: findNode(this.clockInp), + dInp: findNode(this.dInp), + qOutput: findNode(this.qOutput), + qInvOutput: findNode(this.qInvOutput), + // reset: findNode(this.reset), + // preset: findNode(this.preset), + // en: findNode(this.en), + }, + constructorParamaters: [this.direction, this.bitWidth], + + }; + return data; + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + // rect(ctx, xx - 20, yy - 20, 40, 40); + moveTo(ctx, -20, 5, xx, yy, this.direction); + lineTo(ctx, -15, 10, xx, yy, this.direction); + lineTo(ctx, -20, 15, xx, yy, this.direction); + // if ((this.b.hover&&!simulationArea.shiftDown)|| simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)";ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + ctx.font = '20px Georgia'; + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + fillText(ctx, this.state.toString(16), xx, yy + 5); + ctx.fill(); + } +} + +Dlatch.prototype.tooltipText = 'D Latch : Single input Flip flop or D FlipFlop'; +Dlatch.prototype.helplink = 'https://docs.circuitverse.org/#/Sequential?id=d-latch'; + +Dlatch.prototype.objectType = 'Dlatch'; diff --git a/src/sequential/EEPROM.js b/src/sequential/EEPROM.js new file mode 100644 index 0000000..a6519af --- /dev/null +++ b/src/sequential/EEPROM.js @@ -0,0 +1,68 @@ +import RAM from './RAM'; +/** + * @class + * EEPROM Component. + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + + * + * This is basically a RAM component that persists its contents. + * + * We consider EEPROMs more 'expensive' than RAMs, so we arbitrarily limit + * the addressWith to a maximum of 10 bits (1024 addresses) with a default of 8-bit (256). + * + * In the EEPROM all addresses are initialized to zero. + * This way we serialize unused values as "0" instead of "null". + * + * These two techniques help keep reduce the size of saved projects. + * @category sequential + */ +export default class EEPROM extends RAM { + constructor( + x, + y, + scope = globalScope, + dir = 'RIGHT', + bitWidth = 8, + addressWidth = 8, + data = null, + ) { + super(x, y, scope, dir, bitWidth, addressWidth); + /* + this.scope['EEPROM'].push(this); + */ + this.data = data || this.data; + } + + customSave() { + const saveInfo = RAM.prototype.customSave.call(this); + + // Normalize this.data to use zeroes instead of null when serialized. + const { data } = this; + for (let i = 0; i < data.length; i++) { + data[i] = data[i] || 0; + } + + saveInfo.constructorParamaters.push(data); + return saveInfo; + } +} + +EEPROM.prototype.tooltipText = 'Electrically Erasable Programmable Read-Only Memory'; +EEPROM.prototype.shortName = 'EEPROM'; +EEPROM.prototype.maxAddressWidth = 10; +EEPROM.prototype.mutableProperties = { + addressWidth: { + name: 'Address Width', + type: 'number', + max: '10', + min: '1', + func: 'changeAddressWidth', + }, + dump: RAM.prototype.mutableProperties.dump, + reset: RAM.prototype.mutableProperties.reset, +}; +EEPROM.prototype.objectType = 'EEPROM'; diff --git a/src/sequential/JKflipFlop.js b/src/sequential/JKflipFlop.js new file mode 100644 index 0000000..8711ac4 --- /dev/null +++ b/src/sequential/JKflipFlop.js @@ -0,0 +1,146 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText, +} from '../canvasApi'; +/** + * @class + * JKflipFlop + * JK flip flop has 6 input nodes: + * clock, J input, K input, preset, reset ,enable. + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * @category sequential + */ +export default class JKflipFlop extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT') { + super(x, y, scope, dir, 1); + /* + this.scope['JKflipFlop'].push(this); + */ + this.directionFixed = true; + this.fixedBitWidth = true; + this.setDimensions(20, 20); + this.rectangleObject = true; + this.J = new Node(-20, -10, 0, this, 1, 'J'); + this.K = new Node(-20, 0, 0, this, 1, 'K'); + this.clockInp = new Node(-20, 10, 0, this, 1, 'Clock'); + this.qOutput = new Node(20, -10, 1, this, 1, 'Q'); + this.qInvOutput = new Node(20, 10, 1, this, 1, 'Q Inverse'); + this.reset = new Node(10, 20, 0, this, 1, 'Asynchronous Reset'); + this.preset = new Node(0, 20, 0, this, 1, 'Preset'); + this.en = new Node(-10, 20, 0, this, 1, 'Enable'); + this.state = 0; + this.slaveState = 0; + this.masterState = 0; + this.prevClockState = 0; + + // this.wasClicked = false; + } + + /** + * @memberof JKflipFlop + * if none of the predefined nodes have been deleted it isresolvable + */ + isResolvable() { + if (this.reset.value == 1) return true; + if (this.clockInp.value != undefined && this.J.value != undefined && this.K.value != undefined) return true; + return false; + } + + newBitWidth(bitWidth) { + this.bitWidth = bitWidth; + this.dInp.bitWidth = bitWidth; + this.qOutput.bitWidth = bitWidth; + this.qInvOutput.bitWidth = bitWidth; + this.preset.bitWidth = bitWidth; + } + + /** + * @memberof JKflipFlop + * Edge triggered master slave JK flip flop is resolved by + * setting the slaveState = masterState when there is an edge + * in the clock. masterState = this.J when no change in clock. + */ + resolve() { + if (this.reset.value == 1) { + this.masterState = this.slaveState = this.preset.value || 0; + } else if (this.en.value == 0) { + this.prevClockState = this.clockInp.value; + } else if (this.en.value == 1 || this.en.connections.length == 0) { + if (this.clockInp.value == this.prevClockState) { + if (this.clockInp.value == 0 && this.J.value != undefined && this.K.value != undefined) { + if (this.J.value && this.K.value) { this.masterState = 1 ^ this.slaveState; } else if (this.J.value ^ this.K.value) { this.masterState = this.J.value; } + } + } else if (this.clockInp.value != undefined) { + if (this.clockInp.value == 1) { + this.slaveState = this.masterState; + } else if (this.clockInp.value == 0 && this.J.value != undefined && this.K.value != undefined) { + if (this.J.value && this.K.value) { this.masterState = 1 ^ this.slaveState; } else if (this.J.value ^ this.K.value) { this.masterState = this.J.value; } + } + this.prevClockState = this.clockInp.value; + } + } + + if (this.qOutput.value != this.slaveState) { + this.qOutput.value = this.slaveState; + this.qInvOutput.value = this.flipBits(this.slaveState); + simulationArea.simulationQueue.add(this.qOutput); + simulationArea.simulationQueue.add(this.qInvOutput); + } + } + + customSave() { + const data = { + nodes: { + J: findNode(this.J), + K: findNode(this.K), + clockInp: findNode(this.clockInp), + qOutput: findNode(this.qOutput), + qInvOutput: findNode(this.qInvOutput), + reset: findNode(this.reset), + preset: findNode(this.preset), + en: findNode(this.en), + }, + constructorParamaters: [this.direction], + + }; + return data; + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + + // rect(ctx, xx - 20, yy - 20, 40, 40); + moveTo(ctx, -20, 5, xx, yy, this.direction); + lineTo(ctx, -15, 10, xx, yy, this.direction); + lineTo(ctx, -20, 15, xx, yy, this.direction); + + + // if ((this.b.hover&&!simulationArea.shiftDown)|| simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)";ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.font = '20px Georgia'; + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + fillText(ctx, this.slaveState.toString(16), xx, yy + 5); + ctx.fill(); + } +} + +JKflipFlop.prototype.tooltipText = 'JK FlipFlop ToolTip : gated SR flip-flop with the addition of a clock input.'; + +JKflipFlop.prototype.helplink = 'https://docs.circuitverse.org/#/Sequential?id=jk-flip-flop'; + +JKflipFlop.prototype.objectType = 'JKflipFlop'; diff --git a/src/sequential/Keyboard.js b/src/sequential/Keyboard.js new file mode 100644 index 0000000..fdb169c --- /dev/null +++ b/src/sequential/Keyboard.js @@ -0,0 +1,182 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText3, fontSize, +} from '../canvasApi'; +/** + * @class + * Keyboard + * KeyBoard - We can give 3 inputs: clock, enable and available. + * An output of 7 bits is given out when clockInp = 1. + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * @category sequential + */ +export default class Keyboard extends CircuitElement { + constructor(x, y, scope = globalScope, bufferSize = 32) { + super(x, y, scope, 'RIGHT', 1); + /* + this.scope['Keyboard'].push(this); + */ + this.directionFixed = true; + this.fixedBitWidth = true; + + this.bufferSize = bufferSize || parseInt(prompt('Enter buffer size:')); + this.elementWidth = Math.max(80, Math.ceil(this.bufferSize / 2) * 20); + this.elementHeight = 40; // Math.max(40,Math.ceil(this.rows*15/20)*20); + this.setWidth(this.elementWidth / 2); + this.setHeight(this.elementHeight / 2); + + this.clockInp = new Node(-this.elementWidth / 2, this.elementHeight / 2 - 10, 0, this, 1, 'Clock'); + this.asciiOutput = new Node(30, this.elementHeight / 2, 1, this, 7, 'Ascii Output'); + this.available = new Node(10, this.elementHeight / 2, 1, this, 1, 'Available'); + this.reset = new Node(-10, this.elementHeight / 2, 0, this, 1, 'Reset'); + this.en = new Node(-30, this.elementHeight / 2, 0, this, 1, 'Enable'); + this.prevClockState = 0; + this.buffer = ''; + this.bufferOutValue = undefined; + } + + /** + * @memberof Keyboard + * this funcion sets the size of maximum input that can + * be given to the keyboard at once before it starts sending data. + */ + changeBufferSize(size) { + if (size == undefined || size < 20 || size > 100) return; + if (this.bufferSize == size) return; + const obj = new Keyboard(this.x, this.y, this.scope, size); + this.delete(); + simulationArea.lastSelected = obj; + return obj; + } + + /** + * @memberof Keyboard + * Adds the keyy pressed to the buffer + */ + keyDown(key) { + if (key.length != 1) return; + this.buffer += key; + if (this.buffer.length > this.bufferSize) { this.buffer = this.buffer.slice(1); } + // console.log(key) + } + + /** + * @memberof Keyboard + * not resolvable if enable = 0 or clock is undefined + */ + isResolvable() { + if (this.reset.value == 1) return true; + if (this.en.value == 0 || (this.en.connections.length && this.en.value == undefined)) return false; + if (this.clockInp.value == undefined) return false; + return true; + } + + /** + * @memberof Keyboard + * Whenever clock is enabled (1) then one charecter + * from the buffer is converted to ascii and transmitted + * through the output nodes. + */ + resolve() { + if (this.reset.value == 1) { + this.buffer = ''; + return; + } + if (this.en.value == 0) { + return; + } + + if (this.available.value != 0) { + this.available.value = 0; // this.bufferOutValue; + simulationArea.simulationQueue.add(this.available); + } + + if (this.clockInp.value == this.prevClockState) { + if (this.clockInp.value == 0) { + if (this.buffer.length) { + this.bufferOutValue = this.buffer[0].charCodeAt(0); + } else { + this.bufferOutValue = undefined; + } + } + } else if (this.clockInp.value != undefined) { + if (this.clockInp.value == 1 && this.buffer.length) { + if (this.bufferOutValue == this.buffer[0].charCodeAt(0)) { // WHY IS THIS REQUIRED ?? + this.buffer = this.buffer.slice(1); + } + } else if (this.buffer.length) { + this.bufferOutValue = this.buffer[0].charCodeAt(0); + } else { + this.bufferOutValue = undefined; + } + this.prevClockState = this.clockInp.value; + } + + if (this.asciiOutput.value != this.bufferOutValue) { + this.asciiOutput.value = this.bufferOutValue; + simulationArea.simulationQueue.add(this.asciiOutput); + } + + if (this.bufferOutValue !== undefined && this.available.value != 1) { + this.available.value = 1; // this.bufferOutValue; + simulationArea.simulationQueue.add(this.available); + } + } + + customSave() { + const data = { + nodes: { + clockInp: findNode(this.clockInp), + asciiOutput: findNode(this.asciiOutput), + available: findNode(this.available), + reset: findNode(this.reset), + en: findNode(this.en), + }, + constructorParamaters: [this.bufferSize], + }; + return data; + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + moveTo(ctx, -this.elementWidth / 2, this.elementHeight / 2 - 15, xx, yy, this.direction); + lineTo(ctx, 5 - this.elementWidth / 2, this.elementHeight / 2 - 10, xx, yy, this.direction); + lineTo(ctx, -this.elementWidth / 2, this.elementHeight / 2 - 5, xx, yy, this.direction); + + ctx.stroke(); + + ctx.beginPath(); + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + const lineData = this.buffer + ' '.repeat(this.bufferSize - this.buffer.length); + fillText3(ctx, lineData, 0, +5, xx, yy, 15, 'Courier New', 'center'); + ctx.fill(); + } +} + +Keyboard.prototype.tooltipText = 'Keyboard'; +Keyboard.prototype.helplink = 'https://docs.circuitverse.org/#/Sequential?id=keyboard'; + +Keyboard.prototype.mutableProperties = { + bufferSize: { + name: 'Buffer Size', + type: 'number', + max: '100', + min: '20', + func: 'changeBufferSize', + }, +}; + +Keyboard.prototype.objectType = 'Keyboard'; diff --git a/src/sequential/RAM.js b/src/sequential/RAM.js new file mode 100644 index 0000000..47bb214 --- /dev/null +++ b/src/sequential/RAM.js @@ -0,0 +1,204 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, fillText2, fillText4, drawCircle2, +} from '../canvasApi'; +/** + * @class + * RAM Component. + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * + * Two settings are available: + * - addressWidth: 1 to 20, default=10. Controls the width of the address input. + * - bitWidth: 1 to 32, default=8. Controls the width of data pins. + * + * Amount of memory in the element is 2^addressWidth x bitWidth bits. + * Minimum RAM size is: 2^1 x 1 = 2 bits. + * Maximum RAM size is: 2^20 x 32 = 1M x 32 bits => 32 Mbits => 4MB. + * Maximum 8-bits size: 2^20 x 8 = 1M x 8 bits => 1MB. + * Default RAM size is: 2^10 x 8 = 1024 bytes => 1KB. + * + * RAMs are volatile therefore this component does not persist the memory contents. + * + * Changes to addressWidth and bitWidth also cause data to be lost. + * Think of these operations as being equivalent to taking a piece of RAM out of a + * circuit board and replacing it with another RAM of different size. + * + * The contents of the RAM can be reset to zero by setting the RESET pin 1 or + * or by selecting the component and pressing the "Reset" button in the properties window. + * + * The contents of the RAM can be dumped to the console by transitioning CORE DUMP pin to 1 + * or by selecting the component and pressing the "Core Dump" button in the properties window. + * Address spaces that have not been written will show up as `undefined` in the core dump. + * + * NOTE: The maximum address width of 20 is arbitrary. + * Larger values are possible, but in practice circuits won't need this much + * memory and keeping the value small helps avoid allocating too much memory on the browser. + * Internally we use a sparse array, so only the addresses that are written are actually + * allocated. Nevertheless, it is better to prevent large allocations from happening + * by keeping the max addressWidth small. If needed, we can increase the max. + * @category sequential + */ +export default class RAM extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 8, addressWidth = 10) { + super(x, y, scope, dir, Math.min(Math.max(1, bitWidth), 32)); + /* + this.scope['RAM'].push(this); + */ + this.setDimensions(60, 40); + + this.directionFixed = true; + this.labelDirection = 'UP'; + + this.addressWidth = Math.min(Math.max(1, addressWidth), this.maxAddressWidth); + this.address = new Node(-this.leftDimensionX, -20, 0, this, this.addressWidth, 'ADDRESS'); + this.dataIn = new Node(-this.leftDimensionX, 0, 0, this, this.bitWidth, 'DATA IN'); + this.write = new Node(-this.leftDimensionX, 20, 0, this, 1, 'WRITE'); + this.reset = new Node(0, this.downDimensionY, 0, this, 1, 'RESET'); + this.coreDump = new Node(-20, this.downDimensionY, 0, this, 1, 'CORE DUMP'); + this.dataOut = new Node(this.rightDimensionX, 0, 1, this, this.bitWidth, 'DATA OUT'); + this.prevCoreDumpValue = undefined; + + this.clearData(); + } + + customSave() { + return { + // NOTE: data is not persisted since RAMs are volatile. + constructorParamaters: [this.direction, this.bitWidth, this.addressWidth], + nodes: { + address: findNode(this.address), + dataIn: findNode(this.dataIn), + write: findNode(this.write), + reset: findNode(this.reset), + coreDump: findNode(this.coreDump), + dataOut: findNode(this.dataOut), + }, + }; + } + + newBitWidth(value) { + value = parseInt(value); + if (!isNaN(value) && this.bitWidth != value && value >= 1 && value <= 32) { + this.bitWidth = value; + this.dataIn.bitWidth = value; + this.dataOut.bitWidth = value; + this.clearData(); + } + } + + changeAddressWidth(value) { + value = parseInt(value); + if (!isNaN(value) && this.addressWidth != value && value >= 1 && value <= this.maxAddressWidth) { + this.addressWidth = value; + this.address.bitWidth = value; + this.clearData(); + } + } + + clearData() { + this.data = new Array(Math.pow(2, this.addressWidth)); + this.tooltipText = `${this.memSizeString()} ${this.shortName}`; + } + + isResolvable() { + return this.address.value !== undefined || this.reset.value !== undefined || this.coreDump.value !== undefined; + } + + resolve() { + if (this.write.value == 1) { + this.data[this.address.value] = this.dataIn.value; + } + + if (this.reset.value == 1) { + this.clearData(); + } + + if (this.coreDump.value && this.prevCoreDumpValue != this.coreDump.value) { + this.dump(); + } + this.prevCoreDumpValue = this.coreDump.value; + + this.dataOut.value = this.data[this.address.value] || 0; + simulationArea.simulationQueue.add(this.dataOut); + } + + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + + ctx.beginPath(); + ctx.strokeStyle = 'gray'; + ctx.fillStyle = this.write.value ? 'red' : 'lightgreen'; + ctx.lineWidth = correctWidth(1); + drawCircle2(ctx, 50, -30, 3, xx, yy, this.direction); + ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.textAlign = 'center'; + ctx.fillStyle = 'black'; + fillText4(ctx, this.memSizeString(), 0, -10, xx, yy, this.direction, 12); + fillText4(ctx, this.shortName, 0, 10, xx, yy, this.direction, 12); + fillText2(ctx, 'A', this.address.x + 12, this.address.y, xx, yy, this.direction); + fillText2(ctx, 'DI', this.dataIn.x + 12, this.dataIn.y, xx, yy, this.direction); + fillText2(ctx, 'W', this.write.x + 12, this.write.y, xx, yy, this.direction); + fillText2(ctx, 'DO', this.dataOut.x - 15, this.dataOut.y, xx, yy, this.direction); + ctx.fill(); + } + + memSizeString() { + const mag = ['', 'K', 'M']; + const unit = this.bitWidth == 8 ? 'B' : this.bitWidth == 1 ? 'b' : ` x ${this.bitWidth}b`; + let v = Math.pow(2, this.addressWidth); + let m = 0; + while (v >= 1024 && m < mag.length - 1) { + v /= 1024; + m++; + } + return v + mag[m] + unit; + } + + dump() { + const logLabel = console.group && this.label; + if (logLabel) { + console.group(this.label); + } + + console.log(this.data); + + if (logLabel) { + console.groupEnd(); + } + } +} + +RAM.prototype.tooltipText = 'Random Access Memory'; +RAM.prototype.shortName = 'RAM'; +RAM.prototype.maxAddressWidth = 20; +RAM.prototype.mutableProperties = { + addressWidth: { + name: 'Address Width', + type: 'number', + max: '20', + min: '1', + func: 'changeAddressWidth', + }, + dump: { + name: 'Core Dump', + type: 'button', + func: 'dump', + }, + reset: { + name: 'Reset', + type: 'button', + func: 'clearData', + }, +}; +RAM.prototype.objectType = 'RAM'; diff --git a/src/sequential/Rom.js b/src/sequential/Rom.js new file mode 100755 index 0000000..037bc90 --- /dev/null +++ b/src/sequential/Rom.js @@ -0,0 +1,201 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, rect2, fillText3, +} from '../canvasApi'; +/** + * @class + * Rom + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {Array=} data - bit width per node. + * @category sequential + */ +export default class Rom extends CircuitElement { + constructor( + x, + y, + scope = globalScope, + data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ) { + super(x, y, scope, 'RIGHT', 1); + /* + this.scope['Rom'].push(this); + */ + this.fixedBitWidth = true; + this.directionFixed = true; + this.rectangleObject = false; + this.setDimensions(80, 50); + this.memAddr = new Node(-80, 0, 0, this, 4, 'Address'); + this.en = new Node(0, 50, 0, this, 1, 'Enable'); + this.dataOut = new Node(80, 0, 1, this, 8, 'DataOut'); + this.data = data || prompt('Enter data').split(' ').map((lambda) => parseInt(lambda, 16)); + // console.log(this.data); + } + + /** + * @memberof Rom + * Checks if the element is resolvable + * @return {boolean} + */ + isResolvable() { + if ((this.en.value === 1 || this.en.connections.length === 0) && this.memAddr.value !== undefined) return true; + return false; + } + + /** + * @memberof Rom + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.data], + nodes: { + memAddr: findNode(this.memAddr), + dataOut: findNode(this.dataOut), + en: findNode(this.en), + }, + + }; + return data; + } + + /** + * @memberof Rom + * function to find position of the index of part of rom selected. + * @return {number} + */ + findPos() { + const i = Math.floor((simulationArea.mouseX - this.x + 35) / 20); + const j = Math.floor((simulationArea.mouseY - this.y + 35) / 16); + if (i < 0 || j < 0 || i > 3 || j > 3) return undefined; + return j * 4 + i; + } + + /** + * @memberof Rom + * listener function to set selected index + * @return {number} + */ + click() { // toggle + this.selectedIndex = this.findPos(); + } + + /** + * @memberof Rom + * to take input in rom + * @return {number} + */ + keyDown(key) { + if (key === 'Backspace') this.delete(); + if (this.selectedIndex === undefined) return; + key = key.toLowerCase(); + if (!~'1234567890abcdef'.indexOf(key)) return; + + this.data[this.selectedIndex] = (this.data[this.selectedIndex] * 16 + parseInt(key, 16)) % 256; + } + + /** + * @memberof Rom + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + const hoverIndex = this.findPos(); + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + ctx.beginPath(); + rect2(ctx, -this.leftDimensionX, -this.upDimensionY, this.leftDimensionX + this.rightDimensionX, this.upDimensionY + this.downDimensionY, this.x, this.y, [this.direction, 'RIGHT'][+this.directionFixed]); + if (hoverIndex === undefined && ((!simulationArea.shiftDown && this.hover) || simulationArea.lastSelected === this || simulationArea.multipleObjectSelections.contains(this))) ctx.fillStyle = 'rgba(255, 255, 32,0.8)'; + ctx.fill(); + ctx.stroke(); + ctx.strokeStyle = 'black'; + ctx.fillStyle = '#fafafa'; + ctx.lineWidth = correctWidth(1); + ctx.beginPath(); + for (let i = 0; i < 16; i += 4) { + for (let j = i; j < i + 4; j++) { + rect2(ctx, (j % 4) * 20, i * 4, 20, 16, xx - 35, yy - 35); + } + } + ctx.fill(); + ctx.stroke(); + if (hoverIndex !== undefined) { + ctx.beginPath(); + ctx.fillStyle = 'yellow'; + rect2(ctx, (hoverIndex % 4) * 20, Math.floor(hoverIndex / 4) * 16, 20, 16, xx - 35, yy - 35); + ctx.fill(); + ctx.stroke(); + } + if (this.selectedIndex !== undefined) { + ctx.beginPath(); + ctx.fillStyle = 'lightgreen'; + rect2(ctx, (this.selectedIndex % 4) * 20, Math.floor(this.selectedIndex / 4) * 16, 20, 16, xx - 35, yy - 35); + ctx.fill(); + ctx.stroke(); + } + if (this.memAddr.value !== undefined) { + ctx.beginPath(); + ctx.fillStyle = 'green'; + rect2(ctx, (this.memAddr.value % 4) * 20, Math.floor(this.memAddr.value / 4) * 16, 20, 16, xx - 35, yy - 35); + ctx.fill(); + ctx.stroke(); + } + + ctx.beginPath(); + ctx.fillStyle = 'Black'; + fillText3(ctx, 'A', -65, 5, xx, yy, 16, 'Georgia', 'right'); + fillText3(ctx, 'D', 75, 5, xx, yy, 16, 'Georgia', 'right'); + fillText3(ctx, 'En', 5, 47, xx, yy, 16, 'Georgia', 'right'); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = 'Black'; + for (let i = 0; i < 16; i += 4) { + for (let j = i; j < i + 4; j++) { + let s = this.data[j].toString(16); + if (s.length < 2) s = `0${s}`; + fillText3(ctx, s, (j % 4) * 20, i * 4, xx - 35 + 10, yy - 35 + 12, 14, 'Georgia', 'center'); + } + } + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = 'Black'; + for (let i = 0; i < 16; i += 4) { + let s = i.toString(16); + if (s.length < 2) s = `0${s}`; + fillText3(ctx, s, 0, i * 4, xx - 40, yy - 35 + 12, 14, 'Georgia', 'right'); + } + ctx.fill(); + } + + /** + * @memberof Rom + * resolve output values based on inputData + */ + resolve() { + if (this.isResolvable() === false) { + return; + } + this.dataOut.value = this.data[this.memAddr.value]; + simulationArea.simulationQueue.add(this.dataOut); + } +} + +/** + * @memberof Rom + * Help Tip + * @type {string} + * @category sequential + */ +Rom.prototype.tooltipText = 'Read-only memory'; +Rom.prototype.helplink = 'https://docs.circuitverse.org/#/memoryElements?id=rom'; +Rom.prototype.objectType = 'Rom'; diff --git a/src/sequential/SRflipFlop.js b/src/sequential/SRflipFlop.js new file mode 100644 index 0000000..75d0610 --- /dev/null +++ b/src/sequential/SRflipFlop.js @@ -0,0 +1,128 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { correctWidth, fillText } from '../canvasApi'; +/** + * @class + * SRflipFlop + * SR flip flop has 6 input nodes: + * clock, S input, R input, preset, reset ,enable. + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * @category sequential + */ +export default class SRflipFlop extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT') { + super(x, y, scope, dir, 1); + /* + this.scope['SRflipFlop'].push(this); + */ + this.directionFixed = true; + this.fixedBitWidth = true; + this.setDimensions(20, 20); + this.rectangleObject = true; + this.R = new Node(-20, +10, 0, this, 1, 'R'); + this.S = new Node(-20, -10, 0, this, 1, 'S'); + this.qOutput = new Node(20, -10, 1, this, 1, 'Q'); + this.qInvOutput = new Node(20, 10, 1, this, 1, 'Q Inverse'); + this.reset = new Node(10, 20, 0, this, 1, 'Asynchronous Reset'); + this.preset = new Node(0, 20, 0, this, 1, 'Preset'); + this.en = new Node(-10, 20, 0, this, 1, 'Enable'); + this.state = 0; + // this.slaveState = 0; + // this.prevClockState = 0; + // this.wasClicked = false; + } + + newBitWidth(bitWidth) { + this.bitWidth = bitWidth; + this.dInp.bitWidth = bitWidth; + this.qOutput.bitWidth = bitWidth; + this.qInvOutput.bitWidth = bitWidth; + this.preset.bitWidth = bitWidth; + } + + /** + * @memberof SRflipFlop + * always resolvable + */ + isResolvable() { + return true; + if (this.reset.value == 1) return true; + if (this.S.value != undefined && this.R.value != undefined) return true; + return false; + } + + /** + * @memberof SRflipFlop + * function to resolve SR flip flop if S != R we can + * set this.state to value S. + */ + resolve() { + if (this.reset.value == 1) { + this.state = this.preset.value || 0; + } else if ((this.en.value == 1 || this.en.connections == 0) && this.S.value ^ this.R.value) { + this.state = this.S.value; + } + + // console.log(this.reset.value != 1 && this.en.value && this.S.value && this.R.value && this.S.value ^ this.R.value); + if (this.qOutput.value != this.state) { + this.qOutput.value = this.state; + this.qInvOutput.value = this.flipBits(this.state); + simulationArea.simulationQueue.add(this.qOutput); + simulationArea.simulationQueue.add(this.qInvOutput); + } + } + + customSave() { + const data = { + nodes: { + S: findNode(this.S), + R: findNode(this.R), + qOutput: findNode(this.qOutput), + qInvOutput: findNode(this.qInvOutput), + reset: findNode(this.reset), + preset: findNode(this.preset), + en: findNode(this.en), + }, + constructorParamaters: [this.direction], + + }; + return data; + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + + // rect(ctx, xx - 20, yy - 20, 40, 40); + // moveTo(ctx, -20, 5, xx, yy, this.direction); + // lineTo(ctx, -15, 10, xx, yy, this.direction); + // lineTo(ctx, -20, 15, xx, yy, this.direction); + + + // if ((this.b.hover&&!simulationArea.shiftDown)|| simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)";ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.font = '20px Georgia'; + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + fillText(ctx, this.state.toString(16), xx, yy + 5); + ctx.fill(); + } +} + +SRflipFlop.prototype.tooltipText = 'SR FlipFlop ToolTip : SR FlipFlop Selected.'; + +SRflipFlop.prototype.helplink = 'https://docs.circuitverse.org/#/Sequential?id=sr-flip-flop'; + +SRflipFlop.prototype.objectType = 'SRflipFlop'; diff --git a/src/sequential/TTY.js b/src/sequential/TTY.js new file mode 100644 index 0000000..cc96419 --- /dev/null +++ b/src/sequential/TTY.js @@ -0,0 +1,187 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText3, +} from '../canvasApi'; + +/** + * @class + * TTY + * TypeWriter - We can give 4 inputs: + * clock and input of 7 bits are main input required + * on the edge change the data is added onto the display + * screen of the typewriter + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * @category sequential + */ +export default class TTY extends CircuitElement { + constructor(x, y, scope = globalScope, rows = 3, cols = 32) { + super(x, y, scope, 'RIGHT', 1); + /* + this.scope['TTY'].push(this); + */ + this.directionFixed = true; + this.fixedBitWidth = true; + this.cols = cols || parseInt(prompt('Enter cols:')); + this.rows = rows || parseInt(prompt('Enter rows:')); + + this.elementWidth = Math.max(40, Math.ceil(this.cols / 2) * 20); + this.elementHeight = Math.max(40, Math.ceil(this.rows * 15 / 20) * 20); + this.setWidth(this.elementWidth / 2); + this.setHeight(this.elementHeight / 2); + // this.element = new Element(x, y, "TTY",this.elementWidth/2, this,this.elementHeight/2); + + this.clockInp = new Node(-this.elementWidth / 2, this.elementHeight / 2 - 10, 0, this, 1, 'Clock'); + this.asciiInp = new Node(-this.elementWidth / 2, this.elementHeight / 2 - 30, 0, this, 7, 'Ascii Input'); + // this.qOutput = new Node(20, -10, 1, this); + this.reset = new Node(30 - this.elementWidth / 2, this.elementHeight / 2, 0, this, 1, 'Reset'); + this.en = new Node(10 - this.elementWidth / 2, this.elementHeight / 2, 0, this, 1, 'Enable'); + // this.masterState = 0; + // this.slaveState = 0; + this.prevClockState = 0; + + this.data = ''; + this.buffer = ''; + } + + /** + * @memberof TTY + * this funciton is used to change the size of the screen + */ + changeRowSize(size) { + if (size == undefined || size < 1 || size > 10) return; + if (this.rows == size) return; + const obj = new TTY(this.x, this.y, this.scope, size, this.cols); + this.delete(); + simulationArea.lastSelected = obj; + return obj; + } + + /** + * @memberof TTY + * this funciton is used to change the size of the screen + */ + changeColSize(size) { + if (size == undefined || size < 20 || size > 100) return; + if (this.cols == size) return; + const obj = new TTY(this.x, this.y, this.scope, this.rows, size); + this.delete(); + simulationArea.lastSelected = obj; + return obj; + } + + /** + * @memberof TTY + * if no input or enable key is set to 0 returns false + */ + isResolvable() { + if (this.reset.value == 1) return true; + if (this.en.value == 0 || (this.en.connections.length && this.en.value == undefined)) return false; + if (this.clockInp.value == undefined) return false; + if (this.asciiInp.value == undefined) return false; + return true; + } + + /** + * @memberof TTY + * To resolve the Typewriter clock and input of 7 bits are + * used to get the ascii and then on the edge change the + * data is added onto the display screen of the typewriter. + */ + resolve() { + if (this.reset.value == 1) { + this.data = ''; + return; + } + if (this.en.value == 0) { + this.buffer = ''; + return; + } + + if (this.clockInp.value == this.prevClockState) { + if (this.clockInp.value == 0) { + this.buffer = String.fromCharCode(this.asciiInp.value); + } + } else if (this.clockInp.value != undefined) { + if (this.clockInp.value == 1) { + this.data += this.buffer; + if (this.data.length > this.cols * this.rows) { this.data = this.data.slice(1); } + } else if (this.clockInp.value == 0) { + this.buffer = String.fromCharCode(this.asciiInp.value); + } + this.prevClockState = this.clockInp.value; + } + } + + customSave() { + const data = { + nodes: { + clockInp: findNode(this.clockInp), + asciiInp: findNode(this.asciiInp), + reset: findNode(this.reset), + en: findNode(this.en), + }, + constructorParamaters: [this.rows, this.cols], + }; + return data; + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + // rect(ctx, xx - this.elementWidth/2, yy - this.elementHeight/2, this.elementWidth, this.elementHeight); + + moveTo(ctx, -this.elementWidth / 2, this.elementHeight / 2 - 15, xx, yy, this.direction); + lineTo(ctx, 5 - this.elementWidth / 2, this.elementHeight / 2 - 10, xx, yy, this.direction); + lineTo(ctx, -this.elementWidth / 2, this.elementHeight / 2 - 5, xx, yy, this.direction); + + + // if ((this.b.hover&&!simulationArea.shiftDown)|| simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) + // ctx.fillStyle = "rgba(255, 255, 32,0.8)"; + // ctx.fill(); + ctx.stroke(); + + ctx.beginPath(); + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + const startY = -7.5 * this.rows + 3; + for (let i = 0; i < this.data.length; i += this.cols) { + let lineData = this.data.slice(i, i + this.cols); + lineData += ' '.repeat(this.cols - lineData.length); + fillText3(ctx, lineData, 0, startY + (i / this.cols) * 15 + 9, xx, yy, 15, 'Courier New', 'center'); + } + ctx.fill(); + } +} + +TTY.prototype.tooltipText = 'TTY ToolTip : Tele typewriter selected.'; +TTY.prototype.helplink = 'https://docs.circuitverse.org/#/Sequential?id=tty'; + +TTY.prototype.mutableProperties = { + cols: { + name: 'Columns', + type: 'number', + max: '100', + min: '20', + func: 'changeColSize', + }, + rows: { + name: 'Rows', + type: 'number', + max: '10', + min: '1', + func: 'changeRowSize', + }, +}; + +TTY.prototype.objectType = 'TTY'; diff --git a/src/sequential/TflipFlop.js b/src/sequential/TflipFlop.js new file mode 100644 index 0000000..37fe620 --- /dev/null +++ b/src/sequential/TflipFlop.js @@ -0,0 +1,153 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText, +} from '../canvasApi'; + +/** + * @class + * TflipFlop + * T flip flop has 5 input nodes: + * clock, data input, preset, reset ,enable. + * @extends CircuitElement + * @param {number} x - x coord of element + * @param {number} y - y coord of element + * @param {Scope=} scope - the ciruit in which we want the Element + * @param {string=} dir - direcion in which element has to drawn + * @category sequential + */ +export default class TflipFlop extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT') { + super(x, y, scope, dir, 1); + /* + this.scope['TflipFlop'].push(this); + */ + this.directionFixed = true; + this.fixedBitWidth = true; + this.setDimensions(20, 20); + this.rectangleObject = true; + this.clockInp = new Node(-20, +10, 0, this, 1, 'Clock'); + this.dInp = new Node(-20, -10, 0, this, this.bitWidth, 'T'); + this.qOutput = new Node(20, -10, 1, this, this.bitWidth, 'Q'); + this.qInvOutput = new Node(20, 10, 1, this, this.bitWidth, 'Q Inverse'); + this.reset = new Node(10, 20, 0, this, 1, 'Asynchronous Reset'); + this.preset = new Node(0, 20, 0, this, this.bitWidth, 'Preset'); + this.en = new Node(-10, 20, 0, this, 1, 'Enable'); + this.masterState = 0; + this.slaveState = 0; + this.prevClockState = 0; + + // this.wasClicked = false; + } + + /** + * @memberof TflipFlop + * returns true if clock is defined + */ + isResolvable() { + if (this.reset.value == 1) return true; + if (this.clockInp.value != undefined && this.dInp.value != undefined) return true; + return false; + } + + /** + * @memberof TflipFlop + * @param {number} bitWidth - the new bitwidth + */ + newBitWidth(bitWidth) { + this.bitWidth = bitWidth; + this.dInp.bitWidth = bitWidth; + this.qOutput.bitWidth = bitWidth; + this.qInvOutput.bitWidth = bitWidth; + this.preset.bitWidth = bitWidth; + } + + /** + * @memberof TflipFlop + * On the leading edge of the clock signal (LOW-to-HIGH) the first stage, + * the “master” latches the input condition at D, while the output stage is deactivated. + * On the trailing edge of the clock signal (HIGH-to-LOW) the second “slave” stage is + * now activated, latching on to the output from the first master circuit. + * This fuction sets the value for the node qOutput based on + * the previous state and input of the clock by taking xor. + * We flip the bits to find qInvOutput + */ + resolve() { + if (this.reset.value == 1) { + // if reset bit is set + this.masterState = this.slaveState = this.preset.value || 0; + } else if (this.en.value == 0) { + // if enabled bit is 0 + this.prevClockState = this.clockInp.value; + } else if (this.en.value == 1 || this.en.connections.length == 0) { + // if enabled bit is 1 or not connected to anything. + if (this.clockInp.value == this.prevClockState) { + if (this.clockInp.value == 0 && this.dInp.value != undefined) { + // value is xor of + this.masterState = this.dInp.value ^ this.slaveState; + } + } else if (this.clockInp.value != undefined) { + if (this.clockInp.value == 1) { + this.slaveState = this.masterState; + } else if (this.clockInp.value == 0 && this.dInp.value != undefined) { + this.masterState = this.dInp.value ^ this.slaveState; + } + this.prevClockState = this.clockInp.value; + } + } + + if (this.qOutput.value != this.slaveState) { + this.qOutput.value = this.slaveState; + this.qInvOutput.value = this.flipBits(this.slaveState); + simulationArea.simulationQueue.add(this.qOutput); + simulationArea.simulationQueue.add(this.qInvOutput); + } + } + + customSave() { + const data = { + nodes: { + clockInp: findNode(this.clockInp), + dInp: findNode(this.dInp), + qOutput: findNode(this.qOutput), + qInvOutput: findNode(this.qInvOutput), + reset: findNode(this.reset), + preset: findNode(this.preset), + en: findNode(this.en), + }, + constructorParamaters: [this.direction, this.bitWidth], + + }; + return data; + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.fillStyle = 'white'; + ctx.lineWidth = correctWidth(3); + const xx = this.x; + const yy = this.y; + // rect(ctx, xx - 20, yy - 20, 40, 40); + moveTo(ctx, -20, 5, xx, yy, this.direction); + lineTo(ctx, -15, 10, xx, yy, this.direction); + lineTo(ctx, -20, 15, xx, yy, this.direction); + + // if ((this.b.hover&&!simulationArea.shiftDown)|| simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)";ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + ctx.font = '20px Georgia'; + ctx.fillStyle = 'green'; + ctx.textAlign = 'center'; + fillText(ctx, this.slaveState.toString(16), xx, yy + 5); + ctx.fill(); + } +} + +TflipFlop.prototype.tooltipText = 'T FlipFlop ToolTip : Changes state / Toggles whenever the clock input is strobed.'; + +TflipFlop.prototype.helplink = 'https://docs.circuitverse.org/#/Sequential?id=t-flip-flop'; + +TflipFlop.prototype.objectType = 'TflipFlop'; diff --git a/src/setup.js b/src/setup.js new file mode 100755 index 0000000..3317069 --- /dev/null +++ b/src/setup.js @@ -0,0 +1,174 @@ +/* eslint-disable import/no-cycle */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable guard-for-in */ +import * as metadata from './metadata.json'; +import { generateId, showMessage } from './utils'; +import backgroundArea from './backgroundArea'; +import plotArea from './plotArea'; +import simulationArea from './simulationArea'; +import { dots } from './canvasApi'; +import { update, updateSimulationSet, updateCanvasSet } from './engine'; +import { setupUI } from './ux'; +import startMainListeners from './listeners'; +import startEmbedListeners from './embedListeners'; +import './embed'; +import { newCircuit } from './circuit'; +import load from './data/load'; +import save from './data/save'; + +window.width = undefined; +window.height = undefined; +window.DPR = 1; // devicePixelRatio, 2 for retina displays, 1 for low resolution displays + +/** + * to resize window and setup things it + * sets up new width for the canvas variables. + * Also redraws the grid. + * @category setup + */ +export function resetup() { + DPR = window.devicePixelRatio || 1; + if (lightMode) { DPR = 1; } + width = document.getElementById('simulationArea').clientWidth * DPR; + if (!embed) { + height = (document.getElementById('simulation').clientHeight - document.getElementById('toolbar').clientHeight) * DPR; + } else { + height = (document.getElementById('simulation').clientHeight) * DPR; + } + // setup simulationArea and backgroundArea variables used to make changes to canvas. + backgroundArea.setup(); + if (!embed) plotArea.setup(); + simulationArea.setup(); + // redraw grid + dots(); + document.getElementById('backgroundArea').style.height = height / DPR + 100; + document.getElementById('backgroundArea').style.width = width / DPR + 100; + document.getElementById('canvasArea').style.height = height / DPR; + simulationArea.canvas.width = width; + simulationArea.canvas.height = height; + backgroundArea.canvas.width = width + 100 * DPR; + backgroundArea.canvas.height = height + 100 * DPR; + if (!embed) { + plotArea.c.width = document.getElementById('plot').clientWidth; + plotArea.c.height = document.getElementById('plot').clientHeight; + } + updateCanvasSet(true); + update(); // INEFFICIENT, needs to be deprecated + simulationArea.prevScale = 0; + dots(); +} + +window.onresize = resetup; // listener +window.onorientationchange = resetup; // listener + +// for mobiles +window.addEventListener('orientationchange', resetup); // listener + +/** + * function to setup environment variables like projectId and DPR + * @category setup + */ +function setupEnvironment() { + const projectId = generateId(); + updateSimulationSet(true); + const DPR = window.devicePixelRatio || 1; + newCircuit('Main'); + window.data = {}; + resetup(); +} + +/** + * It initializes some useful array which are helpful + * while simulating, saving and loading project. + * It also draws icons in the sidebar + * @category setup + */ +function setupElementLists() { + $('#menu').empty(); + + window.circuitElementList = metadata.circuitElementList; + window.annotationList = metadata.annotationList; + window.inputList = metadata.inputList; + window.subCircuitInputList = metadata.subCircuitInputList; + window.moduleList = [...circuitElementList, ...annotationList]; + window.updateOrder = ['wires', ...circuitElementList, 'nodes', ...annotationList]; // Order of update + window.renderOrder = [...(moduleList.slice().reverse()), 'wires', 'allNodes']; // Order of render + + + function createIcon(element) { + return `
+ +

${element}

+
`; + } + + window.elementHierarchy = metadata.elementHierarchy; + for (const category in elementHierarchy) { + let htmlIcons = ''; + + const categoryData = elementHierarchy[category]; + + for (let i = 0; i < categoryData.length; i++) { + const element = categoryData[i]; + htmlIcons += createIcon(element); + } + + const accordionData = `
${category}
+
+ ${htmlIcons} +
`; + + $('#menu').append(accordionData); + } +} + +/** + * The first function to be called to setup the whole simulator + * @category setup + */ +export function setup() { + const startListeners = embed ? startEmbedListeners : startMainListeners; + setupElementLists(); + setupEnvironment(); + if (!embed) { setupUI(); } + startListeners(); + + // Load project data after 1 second - needs to be improved, delay needs to be eliminated + setTimeout(() => { + if (logix_project_id !== 0) { + $('.loadingIcon').fadeIn(); + $.ajax({ + url: '/simulator/get_data', + type: 'POST', + beforeSend(xhr) { + xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content')); + }, + data: { + id: logix_project_id, + }, + success(response) { + const data = (response); + if (data) { + load(data); + simulationArea.changeClockTime(data.timePeriod || 500); + } + $('.loadingIcon').fadeOut(); + }, + failure() { + alert('Error: could not load '); + $('.loadingIcon').fadeOut(); + }, + }); + } else if (localStorage.getItem('recover_login') && userSignedIn) { + // Restore unsaved data and save + const data = JSON.parse(localStorage.getItem('recover_login')); + load(data); + localStorage.removeItem('recover'); + localStorage.removeItem('recover_login'); + save(); + } else if (localStorage.getItem('recover')) { + // Restore unsaved data which didn't get saved due to error + showMessage("We have detected that you did not save your last work. Don't worry we have recovered them. Access them using Project->Recover"); + } + }, 1000); +} diff --git a/src/simulationArea.js b/src/simulationArea.js index d4b8d31..b439aa4 100644 --- a/src/simulationArea.js +++ b/src/simulationArea.js @@ -1,8 +1,45 @@ -import { width, height } from "./circuit"; -import EventQueue from "./eventQueue" -import { clockTick } from "./utils"; -export var simulationArea = { - canvas: document.getElementById("simulationArea"), +/* eslint-disable import/no-cycle */ +import EventQueue from './eventQueue'; +import { clockTick } from './utils'; + +/** + * simulation environment object - holds simulation canvas + * @type {Object} simulationArea + * @property {HTMLCanvasElement} canvas + * @property {boolean} selected + * @property {boolean} hover + * @property {number} clockState + * @property {boolean} clockEnabled + * @property {undefined} lastSelected + * @property {Array} stack + * @property {number} prevScale + * @property {number} oldx + * @property {number} oldy + * @property {Array} objectList + * @property {number} maxHeight + * @property {number} maxWidth + * @property {number} minHeight + * @property {number} minWidth + * @property {Array} multipleObjectSelections + * @property {Array} copyList - List of selected elements + * @property {boolean} shiftDown - shift down or not + * @property {boolean} controlDown - contol down or not + * @property {number} timePeriod - time period + * @property {number} mouseX - mouse x + * @property {number} mouseY - mouse y + * @property {number} mouseDownX - mouse click x + * @property {number} mouseDownY - mouse click y + * @property {Array} simulationQueue - simulation queue + * @property {number} clickCount - number of clicks + * @property {string} lock - locked or unlocked + * @property {function} timer - timer + * @property {function} setup - to setup the simulaton area + * @property {function} changeClockTime - change clock time + * @property {function} clear - clear the simulation area + * @category simulationArea + */ +const simulationArea = { + canvas: document.getElementById('simulationArea'), selected: false, hover: false, clockState: 0, @@ -28,34 +65,34 @@ export var simulationArea = { mouseDownY: 0, simulationQueue: undefined, - clickCount: 0, //double click - lock: "unlocked", - timer: function () { - ckickTimer = setTimeout(function () { + clickCount: 0, // double click + lock: 'unlocked', + timer() { + ckickTimer = setTimeout(() => { simulationArea.clickCount = 0; }, 600); }, - setup: function () { - this.canvas = document.getElementById("simulationArea"), + setup() { + this.canvas = document.getElementById('simulationArea'); this.canvas.width = width; this.canvas.height = height; this.simulationQueue = new EventQueue(10000); - this.context = this.canvas.getContext("2d"); - simulationArea.changeClockTime(simulationArea.timePeriod) + this.context = this.canvas.getContext('2d'); + simulationArea.changeClockTime(simulationArea.timePeriod); this.mouseDown = false; }, - changeClockTime: function (t) { + changeClockTime(t) { if (t < 50) return; clearInterval(simulationArea.ClockInterval); - t = t || prompt("Enter Time Period:"); + t = t || prompt('Enter Time Period:'); simulationArea.timePeriod = t; simulationArea.ClockInterval = setInterval(clockTick, t); }, - clear: function () { + clear() { if (!this.context) return; this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - } -} - -const changeClockTime = simulationArea.changeClockTime + }, +}; +export const { changeClockTime } = simulationArea; +export default simulationArea; diff --git a/src/subcircuit.js b/src/subcircuit.js new file mode 100755 index 0000000..7a855c1 --- /dev/null +++ b/src/subcircuit.js @@ -0,0 +1,458 @@ +/* eslint-disable import/no-cycle */ +import Scope, { scopeList, switchCircuit } from './circuit'; +import CircuitElement from './circuitElement'; +import simulationArea from './simulationArea'; +import { scheduleBackup, checkIfBackup } from './data/backupCircuit'; +import { + scheduleUpdate, updateSimulationSet, updateCanvasSet, updateSubcircuitSet, +} from './engine'; +import { loadScope } from './data/load'; +import { showError } from './utils'; + +import Node, { findNode } from './node'; +import { fillText } from './canvasApi'; + + +/** + * Function to load a subcicuit + * @category subcircuit + */ +export function loadSubCircuit(savedData, scope) { + new SubCircuit(savedData.x, savedData.y, scope, savedData.id, savedData); +} + +/** + * Prompt to create subcircuit, shows list of circuits which dont depend on the current circuit + * @param {Scope=} scope + * @category subcircuit + */ +export function createSubCircuitPrompt(scope = globalScope) { + $('#insertSubcircuitDialog').empty(); + let flag = true; + for (id in scopeList) { + if (!scopeList[id].checkDependency(scope.id)) { + flag = false; + $('#insertSubcircuitDialog').append(``); + } + } + if (flag) $('#insertSubcircuitDialog').append('

Looks like there are no other circuits which doesn\'t have this circuit as a dependency. Create a new one!

'); + $('#insertSubcircuitDialog').dialog({ + maxHeight: 350, + width: 250, + maxWidth: 250, + minWidth: 250, + buttons: !flag ? [{ + text: 'Insert SubCircuit', + click() { + if (!$('input[name=subCircuitId]:checked').val()) return; + simulationArea.lastSelected = new SubCircuit(undefined, undefined, globalScope, $('input[name=subCircuitId]:checked').val()); + $(this).dialog('close'); + }, + }] : [], + + }); +} + + +/** + * @class + * @extends CircuitElement + * @param {number} x - x coord of subcircuit + * @param {number} y - y coord of subcircuit + * @param {Scope=} scope - the circuit in which subcircuit has been added + * @param {string} id - the id of the subcircuit scope + * @param {JSON} savedData - the saved data + * @category subcircuit + */ +export default class SubCircuit extends CircuitElement { + constructor(x, y, scope = globalScope, id = undefined, savedData = undefined) { + super(x, y, scope, 'RIGHT', 1); // super call + this.objectType = 'SubCircuit'; + this.scope.SubCircuit.push(this); + this.id = id || prompt('Enter Id: '); + this.directionFixed = true; + this.fixedBitWidth = true; + this.savedData = savedData; + this.inputNodes = []; + this.outputNodes = []; + this.localScope = new Scope(); + const subcircuitScope = scopeList[this.id]; // Scope of the subcircuit + // Error handing + if (subcircuitScope == undefined) { + // if no such scope for subcircuit exists + showError(`SubCircuit : ${(savedData && savedData.title) || this.id} Not found`); + } else if (!checkIfBackup(subcircuitScope)) { + // if there is no input/output nodes there will be no backup + showError(`SubCircuit : ${(savedData && savedData.title) || subcircuitScope.name} is an empty circuit`); + } else if (subcircuitScope.checkDependency(scope.id)) { + // check for cyclic dependency + showError('Cyclic Circuit Error'); + } + // Error handling, cleanup + if (subcircuitScope == undefined || subcircuitScope.checkDependency(scope.id) || !checkIfBackup(subcircuitScope)) { + if (savedData) { + for (let i = 0; i < savedData.inputNodes.length; i++) { + scope.allNodes[savedData.inputNodes[i]].deleted = true; + } + for (let i = 0; i < savedData.outputNodes.length; i++) { + scope.allNodes[savedData.outputNodes[i]].deleted = true; + } + } + return; + } + + + if (this.savedData != undefined) { + updateSubcircuitSet(true); + scheduleUpdate(); + this.version = this.savedData.version || '1.0'; + + this.id = this.savedData.id; + for (let i = 0; i < this.savedData.inputNodes.length; i++) { + this.inputNodes.push(this.scope.allNodes[this.savedData.inputNodes[i]]); + this.inputNodes[i].parent = this; + this.inputNodes[i].layout_id = subcircuitScope.Input[i].layoutProperties.id; + } + for (let i = 0; i < this.savedData.outputNodes.length; i++) { + this.outputNodes.push(this.scope.allNodes[this.savedData.outputNodes[i]]); + this.outputNodes[i].parent = this; + this.outputNodes[i].layout_id = subcircuitScope.Output[i].layoutProperties.id; + } + if (this.version == '1.0') { // For backward compatibility + this.version = '2.0'; + this.x -= subcircuitScope.layout.width / 2; + this.y -= subcircuitScope.layout.height / 2; + for (let i = 0; i < this.inputNodes.length; i++) { + this.inputNodes[i].x = subcircuitScope.Input[i].layoutProperties.x; + this.inputNodes[i].y = subcircuitScope.Input[i].layoutProperties.y; + this.inputNodes[i].leftx = this.inputNodes[i].x; + this.inputNodes[i].lefty = this.inputNodes[i].y; + } + for (let i = 0; i < this.outputNodes.length; i++) { + this.outputNodes[i].x = subcircuitScope.Output[i].layoutProperties.x; + this.outputNodes[i].y = subcircuitScope.Output[i].layoutProperties.y; + this.outputNodes[i].leftx = this.outputNodes[i].x; + this.outputNodes[i].lefty = this.outputNodes[i].y; + } + } + + if (this.version == '2.0') { + this.leftDimensionX = 0; + this.upDimensionY = 0; + this.rightDimensionX = subcircuitScope.layout.width; + this.downDimensionY = subcircuitScope.layout.height; + } + + this.nodeList.extend(this.inputNodes); + this.nodeList.extend(this.outputNodes); + } else { + this.version = '2.0'; + } + + this.data = JSON.parse(scheduleBackup(subcircuitScope)); + this.buildCircuit(); // load the localScope for the subcircuit + this.makeConnections(); // which will be wireless + } + + /** + * actually make all connection but are invisible so + * it seems like the simulation is happening in other + * Scope but it actually is not. + */ + makeConnections() { + for (let i = 0; i < this.inputNodes.length; i++) { + this.localScope.Input[i].output1.connectWireLess(this.inputNodes[i]); + this.localScope.Input[i].output1.subcircuitOverride = true; + } + + for (let i = 0; i < this.outputNodes.length; i++) { + this.localScope.Output[i].inp1.connectWireLess(this.outputNodes[i]); + this.outputNodes[i].subcircuitOverride = true; + } + } + + /** + * Function to remove wireless connections + */ + removeConnections() { + for (let i = 0; i < this.inputNodes.length; i++) { + this.localScope.Input[i].output1.disconnectWireLess(this.inputNodes[i]); + } + + for (let i = 0; i < this.outputNodes.length; i++) { this.localScope.Output[i].inp1.disconnectWireLess(this.outputNodes[i]); } + } + + /** + * loads the subcircuit and draws all the nodes + */ + buildCircuit() { + const subcircuitScope = scopeList[this.id]; + loadScope(this.localScope, this.data); + this.lastUpdated = this.localScope.timeStamp; + updateSimulationSet(true); + updateCanvasSet(true); + + if (this.savedData == undefined) { + this.leftDimensionX = 0; + this.upDimensionY = 0; + this.rightDimensionX = subcircuitScope.layout.width; + this.downDimensionY = subcircuitScope.layout.height; + for (let i = 0; i < subcircuitScope.Output.length; i++) { + const a = new Node(subcircuitScope.Output[i].layoutProperties.x, subcircuitScope.Output[i].layoutProperties.y, 1, this, subcircuitScope.Output[i].bitWidth); + a.layout_id = subcircuitScope.Output[i].layoutProperties.id; + this.outputNodes.push(a); + } + for (let i = 0; i < subcircuitScope.Input.length; i++) { + const a = new Node(subcircuitScope.Input[i].layoutProperties.x, subcircuitScope.Input[i].layoutProperties.y, 0, this, subcircuitScope.Input[i].bitWidth); + a.layout_id = subcircuitScope.Input[i].layoutProperties.id; + this.inputNodes.push(a); + } + } + } + + // Needs to be deprecated, removed + reBuild() { + + // new SubCircuit(x = this.x, y = this.y, scope = this.scope, this.id); + // this.scope.backups = []; // Because all previous states are invalid now + // this.delete(); + // showMessage('Subcircuit: ' + subcircuitScope.name + ' has been reloaded.'); + } + + /** + * rebuilds the subcircuit if any change to localscope is made + */ + reBuildCircuit() { + this.data = JSON.parse(scheduleBackup(scopeList[this.id])); + this.localScope = new Scope(); + loadScope(this.localScope, this.data); + this.lastUpdated = this.localScope.timeStamp; + this.scope.timeStamp = this.localScope.timeStamp; + } + + reset() { + this.removeConnections(); + + const subcircuitScope = scopeList[this.id]; + + for (let i = 0; i < subcircuitScope.SubCircuit.length; i++) { + subcircuitScope.SubCircuit[i].reset(); + } + + if (subcircuitScope.Input.length == 0 && subcircuitScope.Output.length == 0) { + showError(`SubCircuit : ${subcircuitScope.name} is an empty circuit`); + this.delete(); + this.scope.backups = []; + return; + } + + subcircuitScope.layout.height = subcircuitScope.layout.height; + subcircuitScope.layout.width = subcircuitScope.layout.width; + this.leftDimensionX = 0; + this.upDimensionY = 0; + this.rightDimensionX = subcircuitScope.layout.width; + this.downDimensionY = subcircuitScope.layout.height; + + + const temp_map_inp = {}; + for (let i = 0; i < subcircuitScope.Input.length; i++) { + temp_map_inp[subcircuitScope.Input[i].layoutProperties.id] = [subcircuitScope.Input[i], undefined]; + } + for (let i = 0; i < this.inputNodes.length; i++) { + if (temp_map_inp.hasOwnProperty(this.inputNodes[i].layout_id)) { + temp_map_inp[this.inputNodes[i].layout_id][1] = this.inputNodes[i]; + } else { + this.scope.backups = []; + this.inputNodes[i].delete(); + this.nodeList.clean(this.inputNodes[i]); + } + } + + for (id in temp_map_inp) { + if (temp_map_inp[id][1]) { + if (temp_map_inp[id][0].layoutProperties.x == temp_map_inp[id][1].x && temp_map_inp[id][0].layoutProperties.y == temp_map_inp[id][1].y) { temp_map_inp[id][1].bitWidth = temp_map_inp[id][0].bitWidth; } else { + this.scope.backups = []; + temp_map_inp[id][1].delete(); + this.nodeList.clean(temp_map_inp[id][1]); + temp_map_inp[id][1] = new Node(temp_map_inp[id][0].layoutProperties.x, temp_map_inp[id][0].layoutProperties.y, 0, this, temp_map_inp[id][0].bitWidth); + temp_map_inp[id][1].layout_id = id; + } + } + } + + this.inputNodes = []; + for (let i = 0; i < subcircuitScope.Input.length; i++) { + const input = temp_map_inp[subcircuitScope.Input[i].layoutProperties.id][0]; + if (temp_map_inp[input.layoutProperties.id][1]) { this.inputNodes.push(temp_map_inp[input.layoutProperties.id][1]); } else { + const a = new Node(input.layoutProperties.x, input.layoutProperties.y, 0, this, input.bitWidth); + a.layout_id = input.layoutProperties.id; + this.inputNodes.push(a); + } + } + + const temp_map_out = {}; + for (let i = 0; i < subcircuitScope.Output.length; i++) { + temp_map_out[subcircuitScope.Output[i].layoutProperties.id] = [subcircuitScope.Output[i], undefined]; + } + for (let i = 0; i < this.outputNodes.length; i++) { + if (temp_map_out.hasOwnProperty(this.outputNodes[i].layout_id)) { + temp_map_out[this.outputNodes[i].layout_id][1] = this.outputNodes[i]; + } else { + this.outputNodes[i].delete(); + this.nodeList.clean(this.outputNodes[i]); + } + } + + for (id in temp_map_out) { + if (temp_map_out[id][1]) { + if (temp_map_out[id][0].layoutProperties.x == temp_map_out[id][1].x && temp_map_out[id][0].layoutProperties.y == temp_map_out[id][1].y) { temp_map_out[id][1].bitWidth = temp_map_out[id][0].bitWidth; } else { + temp_map_out[id][1].delete(); + this.nodeList.clean(temp_map_out[id][1]); + temp_map_out[id][1] = new Node(temp_map_out[id][0].layoutProperties.x, temp_map_out[id][0].layoutProperties.y, 1, this, temp_map_out[id][0].bitWidth); + temp_map_out[id][1].layout_id = id; + } + } + } + + this.outputNodes = []; + for (let i = 0; i < subcircuitScope.Output.length; i++) { + const output = temp_map_out[subcircuitScope.Output[i].layoutProperties.id][0]; + if (temp_map_out[output.layoutProperties.id][1]) { this.outputNodes.push(temp_map_out[output.layoutProperties.id][1]); } else { + const a = new Node(output.layoutProperties.x, output.layoutProperties.y, 1, this, output.bitWidth); + a.layout_id = output.layoutProperties.id; + this.outputNodes.push(a); + } + } + + if (subcircuitScope.timeStamp > this.lastUpdated) { + this.reBuildCircuit(); + } + + this.localScope.reset(); + + this.makeConnections(); + } + + click() { + // this.id=prompt(); + } + + /** + * adds all local scope inputs to the global scope simulation queue + */ + addInputs() { + for (let i = 0; i < subCircuitInputList.length; i++) { + for (let j = 0; j < this.localScope[subCircuitInputList[i]].length; j++) { simulationArea.simulationQueue.add(this.localScope[subCircuitInputList[i]][j], 0); } + } + for (let j = 0; j < this.localScope.SubCircuit.length; j++) { this.localScope.SubCircuit[j].addInputs(); } + } + + isResolvable() { + if (CircuitElement.prototype.isResolvable.call(this)) { return true; } + return false; + } + + dblclick() { + switchCircuit(this.id); + } + + saveObject() { + const data = { + x: this.x, + y: this.y, + id: this.id, + inputNodes: this.inputNodes.map(findNode), + outputNodes: this.outputNodes.map(findNode), + version: this.version, + }; + return data; + } + + /** + * not used because for now everythiing is added onto the globalscope + */ + resolve() { + // deprecated + // let subcircuitScope = this.localScope;//scopeList[this.id]; + // // this.scope.pending.clean(this); // To remove any pending instances + // // return; + // + // for (i = 0; i < subcircuitScope.Input.length; i++) { + // subcircuitScope.Input[i].state = this.inputNodes[i].value; + // } + // + // for (i = 0; i < subcircuitScope.Input.length; i++) { + // simulationArea.simulationQueue.add(subcircuitScope.Input[i]); + // } + // play(subcircuitScope); + // + // for (i = 0; i < subcircuitScope.Output.length; i++) { + // this.outputNodes[i].value = subcircuitScope.Output[i].inp1.value; + // } + // for (i = 0; i < subcircuitScope.Output.length; i++) { + // this.scope.stack.push(this.outputNodes[i]); + // } + + } + + isResolvable() { + return false; + } + + verilogName() { + return verilog.fixName(scopeList[this.id].name); + } + + /** + * determines where to show label + */ + determine_label(x, y) { + if (x == 0) return ['left', 5, 5]; + if (x == scopeList[this.id].layout.width) return ['right', -5, 5]; + if (y == 0) return ['center', 0, 13]; + return ['center', 0, -6]; + } + + customDraw() { + const subcircuitScope = scopeList[this.id]; + + const ctx = simulationArea.context; + + ctx.lineWidth = globalScope.scale * 3; + ctx.strokeStyle = 'black'; // ("rgba(0,0,0,1)"); + ctx.fillStyle = 'white'; + const xx = this.x; + const yy = this.y; + + ctx.beginPath(); + + ctx.textAlign = 'center'; + ctx.fillStyle = 'black'; + if (this.version == '1.0') { fillText(ctx, subcircuitScope.name, xx, yy - subcircuitScope.layout.height / 2 + 13, 11); } else if (this.version == '2.0') { + if (subcircuitScope.layout.titleEnabled) { + fillText(ctx, subcircuitScope.name, subcircuitScope.layout.title_x + xx, yy + subcircuitScope.layout.title_y, 11); + } + } else { console.log(this.version); } + + for (let i = 0; i < subcircuitScope.Input.length; i++) { + if (!subcircuitScope.Input[i].label) continue; + const info = this.determine_label(this.inputNodes[i].x, this.inputNodes[i].y); + ctx.textAlign = info[0]; + fillText(ctx, subcircuitScope.Input[i].label, this.inputNodes[i].x + info[1] + xx, yy + this.inputNodes[i].y + info[2], 12); + } + + for (let i = 0; i < subcircuitScope.Output.length; i++) { + if (!subcircuitScope.Output[i].label) continue; + const info = this.determine_label(this.outputNodes[i].x, this.outputNodes[i].y); + ctx.textAlign = info[0]; + fillText(ctx, subcircuitScope.Output[i].label, this.outputNodes[i].x + info[1] + xx, yy + this.outputNodes[i].y + info[2], 12); + } + ctx.fill(); + // console.log("input",this.inputNodes) + // console.log("oput",this.outputNodes) + for (let i = 0; i < this.outputNodes.length; i++) { this.outputNodes[i].draw(); } + for (let i = 0; i < this.inputNodes.length; i++) { this.inputNodes[i].draw(); } + } +} + +SubCircuit.prototype.centerElement = true; // To center subcircuit when new diff --git a/src/testbench.js b/src/testbench.js new file mode 100644 index 0000000..eb8f4b8 --- /dev/null +++ b/src/testbench.js @@ -0,0 +1,3 @@ +/** + * this file can contain any specific function for testbench modules + */ diff --git a/src/testbench/ForceGate.js b/src/testbench/ForceGate.js new file mode 100755 index 0000000..c16702e --- /dev/null +++ b/src/testbench/ForceGate.js @@ -0,0 +1,88 @@ +import CircuitElement from '../circuitElement'; +import Node, { findNode } from '../node'; +import simulationArea from '../simulationArea'; +import { fillText4 } from '../canvasApi'; +/** + * @class + * ForceGate + * @extends CircuitElement + * @param {number} x - x coordinate of element. + * @param {number} y - y coordinate of element. + * @param {Scope=} scope - Cirucit on which element is drawn + * @param {string=} dir - direction of element + * @param {number=} bitWidth - bit width per node. + * @category testbench + */ +export default class ForceGate extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', bitWidth = 1) { + super(x, y, scope, dir, bitWidth); + this.setDimensions(20, 10); + this.objectType = 'ForceGate'; + this.scope.ForceGate.push(this); + this.inp1 = new Node(-20, 0, 0, this); + this.inp2 = new Node(0, 0, 0, this); + this.output1 = new Node(20, 0, 1, this); + } + + /** + * @memberof ForceGate + * Checks if the element is resolvable + * @return {boolean} + */ + isResolvable() { + return (this.inp1.value !== undefined || this.inp2.value !== undefined); + } + + /** + * @memberof ForceGate + * fn to create save Json Data of object + * @return {JSON} + */ + customSave() { + const data = { + constructorParamaters: [this.direction, this.bitWidth], + nodes: { + output1: findNode(this.output1), + inp1: findNode(this.inp1), + inp2: findNode(this.inp2), + }, + }; + return data; + } + + /** + * @memberof ForceGate + * resolve output values based on inputData + */ + resolve() { + if (this.inp2.value !== undefined) { this.output1.value = this.inp2.value; } else { this.output1.value = this.inp1.value; } + simulationArea.simulationQueue.add(this.output1); + } + + /** + * @memberof ForceGate + * function to draw element + */ + customDraw() { + const ctx = simulationArea.context; + const xx = this.x; + const yy = this.y; + + ctx.beginPath(); + ctx.fillStyle = 'Black'; + ctx.textAlign = 'center'; + + fillText4(ctx, 'I', -10, 0, xx, yy, this.direction, 10); + fillText4(ctx, 'O', 10, 0, xx, yy, this.direction, 10); + ctx.fill(); + } +} + +/** + * @memberof ForceGate + * Help Tip + * @type {string} + * @category testbench + */ +ForceGate.prototype.tooltipText = 'Force Gate ToolTip : ForceGate Selected.'; +ForceGate.prototype.objectType = 'ForceGate'; diff --git a/src/testbench/testbenchInput.js b/src/testbench/testbenchInput.js new file mode 100644 index 0000000..77d3830 --- /dev/null +++ b/src/testbench/testbenchInput.js @@ -0,0 +1,291 @@ +import CircuitElement from '../circuitElement'; +import simulationArea from '../simulationArea'; +import { + correctWidth, lineTo, moveTo, fillText, +} from '../canvasApi'; +import Node, { findNode } from '../node'; +import plotArea from '../plotArea'; + + +/** + * TestBench Input has a node for it's clock input. + * this.testData - the data of all test cases. + * Every testbench has a uniq identifier. + * @class + * @extends CircuitElement + * @param {number} x - the x coord of TB + * @param {number} y - the y coord of TB + * @param {Scope=} scope - the circuit on which TB is drawn + * @param {string} dir - direction + * @param {string} identifier - id to identify tests + * @param {JSON=} testData - input, output and number of tests + * @category testbench + */ +export default class TB_Input extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', identifier, testData) { + super(x, y, scope, dir, 1); + this.objectType = 'TB_Input'; + this.scope.TB_Input.push(this); + this.setIdentifier(identifier || 'Test1'); + this.testData = testData || { inputs: [], outputs: [], n: 0 }; + this.clockInp = new Node(0, 20, 0, this, 1); + this.outputs = []; + this.running = false; // if tests are undergo + this.iteration = 0; + this.setup(); + } + + /** + * @memberof TB_Input + * Takes iput when double clicked. For help on generation of input refer to TB_Input.helplink + */ + dblclick() { + this.testData = JSON.parse(prompt('Enter TestBench Json')); + this.setup(); + } + + setDimensions() { + this.leftDimensionX = 0; + this.rightDimensionX = 120; + + this.upDimensionY = 0; + this.downDimensionY = 40 + this.testData.inputs.length * 20; + } + + /** + * @memberof TB_Input + * setups the Test by parsing through the testbench data. + */ + setup() { + this.iteration = 0; + this.running = false; + this.nodeList.clean(this.clockInp); + this.deleteNodes(); + this.nodeList = []; + this.nodeList.push(this.clockInp); + this.testData = this.testData || { inputs: [], outputs: [], n: 0 }; + // this.clockInp = new Node(0,20, 0,this,1); + + this.setDimensions(); + + this.prevClockState = 0; + this.outputs = []; + + for (let i = 0; i < this.testData.inputs.length; i++) { + this.outputs.push(new Node(this.rightDimensionX, 30 + i * 20, 1, this, this.testData.inputs[i].bitWidth, this.testData.inputs[i].label)); + } + + for (let i = 0; i < this.scope.TB_Output.length; i++) { + if (this.scope.TB_Output[i].identifier == this.identifier) { this.scope.TB_Output[i].setup(); } + } + } + + /** + * @memberof TB_Input + * toggles state by simply negating this.running so that test cases stop + */ + toggleState() { + this.running = !this.running; + this.prevClockState = 0; + } + + /** + * @memberof TB_Input + * function to run from test case 0 again + */ + resetIterations() { + this.iteration = 0; + this.prevClockState = 0; + } + + /** + * @memberof TB_Input + * function to resolve the testbench input adds + */ + resolve() { + if (this.clockInp.value != this.prevClockState) { + this.prevClockState = this.clockInp.value; + if (this.clockInp.value == 1 && this.running) { + if (this.iteration < this.testData.n) { + this.iteration++; + } else { + this.running = false; + } + } + } + if (this.running && this.iteration) { + for (let i = 0; i < this.testData.inputs.length; i++) { + console.log(this.testData.inputs[i].values[this.iteration - 1]); + this.outputs[i].value = parseInt(this.testData.inputs[i].values[this.iteration - 1], 2); + simulationArea.simulationQueue.add(this.outputs[i]); + } + } + } + + /** + * @memberof TB_Input + * was a function to plot values incase any flag used as output to this element + */ + setPlotValue() { + const time = plotArea.stopWatch.ElapsedMilliseconds; + if (this.plotValues.length && this.plotValues[this.plotValues.length - 1][0] == time) { this.plotValues.pop(); } + + if (this.plotValues.length == 0) { + this.plotValues.push([time, this.inp1.value]); + return; + } + + if (this.plotValues[this.plotValues.length - 1][1] == this.inp1.value) { return; } + this.plotValues.push([time, this.inp1.value]); + } + + customSave() { + const data = { + constructorParamaters: [this.direction, this.identifier, this.testData], + nodes: { + outputs: this.outputs.map(findNode), + clockInp: findNode(this.clockInp), + }, + }; + return data; + } + + /** + * This function is used to set a uniq identifier to every testbench + * @memberof TB_Input + */ + setIdentifier(id = '') { + if (id.length == 0 || id == this.identifier) return; + + + for (let i = 0; i < this.scope.TB_Output.length; i++) { + this.scope.TB_Output[i].checkPairing(); + } + + + for (let i = 0; i < this.scope.TB_Output.length; i++) { + if (this.scope.TB_Output[i].identifier == this.identifier) { this.scope.TB_Output[i].identifier = id; } + } + + this.identifier = id; + + this.checkPaired(); + } + + /** + * Check if there is a output tester paired with input TB. + * @memberof TB_Input + */ + checkPaired() { + for (let i = 0; i < this.scope.TB_Output.length; i++) { + if (this.scope.TB_Output[i].identifier == this.identifier) { this.scope.TB_Output[i].checkPairing(); } + } + } + + delete() { + super.delete(); + this.checkPaired(); + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = 'grey'; + ctx.fillStyle = '#fcfcfc'; + ctx.lineWidth = correctWidth(1); + let xx = this.x; + let yy = this.y; + + let xRotate = 0; + let yRotate = 0; + if (this.direction == 'LEFT') { + xRotate = 0; + yRotate = 0; + } else if (this.direction == 'RIGHT') { + xRotate = 120 - this.xSize; + yRotate = 0; + } else if (this.direction == 'UP') { + xRotate = 60 - this.xSize / 2; + yRotate = -20; + } else { + xRotate = 60 - this.xSize / 2; + yRotate = 20; + } + + ctx.beginPath(); + ctx.textAlign = 'center'; + ctx.fillStyle = 'black'; + fillText(ctx, `${this.identifier} [INPUT]`, xx + this.rightDimensionX / 2, yy + 14, 10); + + fillText(ctx, ['Not Running', 'Running'][+this.running], xx + this.rightDimensionX / 2, yy + 14 + 10 + 20 * this.testData.inputs.length, 10); + fillText(ctx, `Case: ${this.iteration}`, xx + this.rightDimensionX / 2, yy + 14 + 20 + 20 * this.testData.inputs.length, 10); + // fillText(ctx, "Case: "+this.iteration, xx , yy + 20+14, 10); + ctx.fill(); + + + ctx.font = '30px Georgia'; + ctx.textAlign = 'right'; + ctx.fillStyle = 'blue'; + ctx.beginPath(); + for (let i = 0; i < this.testData.inputs.length; i++) { + // ctx.beginPath(); + fillText(ctx, this.testData.inputs[i].label, this.rightDimensionX - 5 + xx, 30 + i * 20 + yy + 4, 10); + } + + ctx.fill(); + if (this.running && this.iteration) { + ctx.font = '30px Georgia'; + ctx.textAlign = 'left'; + ctx.fillStyle = 'blue'; + ctx.beginPath(); + for (let i = 0; i < this.testData.inputs.length; i++) { + fillText(ctx, this.testData.inputs[i].values[this.iteration - 1], 5 + xx, 30 + i * 20 + yy + 4, 10); + } + + ctx.fill(); + } + + ctx.beginPath(); + ctx.strokeStyle = ('rgba(0,0,0,1)'); + ctx.lineWidth = correctWidth(3); + xx = this.x; + yy = this.y; + // rect(ctx, xx - 20, yy - 20, 40, 40); + moveTo(ctx, 0, 15, xx, yy, this.direction); + lineTo(ctx, 5, 20, xx, yy, this.direction); + lineTo(ctx, 0, 25, xx, yy, this.direction); + + ctx.stroke(); + } +} + +TB_Input.prototype.tooltipText = 'Test Bench Input Selected'; + +/** + * @memberof TB_Input + * different algo for drawing center elements + * @category testbench + */ +TB_Input.prototype.centerElement = true; + +TB_Input.prototype.helplink = 'https://docs.circuitverse.org/#/testbench'; + +TB_Input.prototype.mutableProperties = { + identifier: { + name: 'TestBench Name:', + type: 'text', + maxlength: '10', + func: 'setIdentifier', + }, + iteration: { + name: 'Reset Iterations', + type: 'button', + func: 'resetIterations', + }, + toggleState: { + name: 'Toggle State', + type: 'button', + func: 'toggleState', + }, +}; +TB_Input.prototype.objectType = 'TB_Input'; diff --git a/src/testbench/testbenchOutput.js b/src/testbench/testbenchOutput.js new file mode 100644 index 0000000..408d74b --- /dev/null +++ b/src/testbench/testbenchOutput.js @@ -0,0 +1,232 @@ +import CircuitElement from '../circuitElement'; +import simulationArea from '../simulationArea'; +import { correctWidth, fillText } from '../canvasApi'; +import Node, { findNode } from '../node'; + +// helper function to convert decimal to binary +function dec2bin(dec, bitWidth = undefined) { + // only for positive nos + const bin = (dec).toString(2); + if (bitWidth == undefined) return bin; + return '0'.repeat(bitWidth - bin.length) + bin; +} + +/** + * TestBench Output has a node for it's input which is + * compared to desired output according tp testData of + * input TB Every TB_output has a uniq identifier matching + * it's TB_Input + * @class + * @extends CircuitElement + * @param {number} x - the x coord of TB + * @param {number} y - the y coord of TB + * @param {Scope=} scope - the circuit on which TB is drawn + * @param {string} dir - direction + * @param {string} identifier - id to identify tests + * @category testbench + */ + +export default class TB_Output extends CircuitElement { + constructor(x, y, scope = globalScope, dir = 'RIGHT', identifier) { + super(x, y, scope, dir, 1); + // this.setDimensions(60,20); + this.objectType = 'TB_Output'; + this.scope.TB_Output.push(this); + + // this.xSize=10; + + // this.plotValues = []; + // this.inp1 = new Node(0, 0, 0, this); + // this.inp1 = new Node(100, 100, 0, this); + this.setIdentifier(identifier || 'Test1'); + this.inputs = []; + this.testBenchInput = undefined; + + this.setup(); + } + + // TB_Output.prototype.dblclick=function(){ + // this.testData=JSON.parse(prompt("Enter TestBench Json")); + // this.setup(); + // } + setDimensions() { + this.leftDimensionX = 0; + this.rightDimensionX = 160; + this.upDimensionY = 0; + this.downDimensionY = 40; + if (this.testBenchInput) { this.downDimensionY = 40 + this.testBenchInput.testData.outputs.length * 20; } + } + + setup() { + // this.iteration = 0; + // this.running = false; + // this.nodeList.clean(this.clockInp); + this.deleteNodes(); // deletes all nodes whenever setup is called. + this.nodeList = []; + + this.inputs = []; + this.testBenchInput = undefined; + // find it's pair input + for (let i = 0; i < this.scope.TB_Input.length; i++) { + if (this.scope.TB_Input[i].identifier == this.identifier) { + this.testBenchInput = this.scope.TB_Input[i]; + break; + } + } + + this.setDimensions(); + + if (this.testBenchInput) { + for (let i = 0; i < this.testBenchInput.testData.outputs.length; i++) { + this.inputs.push(new Node(0, 30 + i * 20, NODE_INPUT, this, this.testBenchInput.testData.outputs[i].bitWidth, this.testBenchInput.testData.outputs[i].label)); + } + } + } + + customSave() { + const data = { + constructorParamaters: [this.direction, this.identifier], + nodes: { + inputs: this.inputs.map(findNode), + }, + }; + return data; + } + + /** + * @memberof TB_output + * set identifier for this testbench + */ + setIdentifier(id = '') { + if (id.length == 0 || id == this.identifier) return; + this.identifier = id; + this.setup(); + } + + /** + * @memberof TB_output + * Function to check if the input for this TB exist + */ + checkPairing(id = '') { + if (this.testBenchInput) { + if (this.testBenchInput.deleted || this.testBenchInput.identifier != this.identifier) { + this.setup(); + } + } else { + this.setup(); + } + } + + customDraw() { + const ctx = simulationArea.context; + ctx.beginPath(); + ctx.strokeStyle = 'grey'; + ctx.fillStyle = '#fcfcfc'; + ctx.lineWidth = correctWidth(1); + const xx = this.x; + const yy = this.y; + + let xRotate = 0; + let yRotate = 0; + if (this.direction == 'LEFT') { + xRotate = 0; + yRotate = 0; + } else if (this.direction == 'RIGHT') { + xRotate = 120 - this.xSize; + yRotate = 0; + } else if (this.direction == 'UP') { + xRotate = 60 - this.xSize / 2; + yRotate = -20; + } else { + xRotate = 60 - this.xSize / 2; + yRotate = 20; + } + + // rect2(ctx, -120+xRotate+this.xSize, -20+yRotate, 120-this.xSize, 40, xx, yy, "RIGHT"); + // if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) + // ctx.fillStyle = "rgba(255, 255, 32,0.8)"; + // ctx.fill(); + // ctx.stroke(); + // + // ctx.font = "14px Georgia"; + // this.xOff = ctx.measureText(this.identifier).width; + // ctx.beginPath(); + // rect2(ctx, -105+xRotate+this.xSize, -11+yRotate,this.xOff + 10, 23, xx, yy, "RIGHT"); + // ctx.fillStyle = "#eee" + // ctx.strokeStyle = "#ccc"; + // ctx.fill(); + // ctx.stroke(); + // + + + ctx.beginPath(); + ctx.textAlign = 'center'; + ctx.fillStyle = 'black'; + fillText(ctx, `${this.identifier} [OUTPUT]`, xx + this.rightDimensionX / 2, yy + 14, 10); + + // fillText(ctx, ["Not Running","Running"][+this.running], xx + this.rightDimensionX/ 2 , yy + 14 + 10 + 20*this.testData.inputs.length, 10); + // fillText(ctx, "Case: "+(this.iteration), xx + this.rightDimensionX/ 2 , yy + 14 + 20 + 20*this.testData.inputs.length, 10); + fillText(ctx, ['Unpaired', 'Paired'][+(this.testBenchInput != undefined)], xx + this.rightDimensionX / 2, yy + this.downDimensionY - 5, 10); + ctx.fill(); + + + if (this.testBenchInput) { + ctx.beginPath(); + ctx.font = '30px Georgia'; + ctx.textAlign = 'left'; + ctx.fillStyle = 'blue'; + for (let i = 0; i < this.testBenchInput.testData.outputs.length; i++) { + // ctx.beginPath(); + fillText(ctx, this.testBenchInput.testData.outputs[i].label, 5 + xx, 30 + i * 20 + yy + 4, 10); + } + ctx.fill(); + + if (this.testBenchInput.running && this.testBenchInput.iteration) { + ctx.beginPath(); + ctx.font = '30px Georgia'; + ctx.textAlign = 'right'; + ctx.fillStyle = 'blue'; + ctx.beginPath(); + for (let i = 0; i < this.testBenchInput.testData.outputs.length; i++) { + fillText(ctx, this.testBenchInput.testData.outputs[i].values[this.testBenchInput.iteration - 1], xx + this.rightDimensionX - 5, 30 + i * 20 + yy + 4, 10); + } + + ctx.fill(); + } + + if (this.testBenchInput.running && this.testBenchInput.iteration) { + ctx.beginPath(); + ctx.font = '30px Georgia'; + ctx.textAlign = 'center'; + ctx.fillStyle = 'blue'; + + for (let i = 0; i < this.testBenchInput.testData.outputs.length; i++) { + if (this.inputs[i].value != undefined) { + ctx.beginPath(); + if (this.testBenchInput.testData.outputs[i].values[this.testBenchInput.iteration - 1] == 'x' || parseInt(this.testBenchInput.testData.outputs[i].values[this.testBenchInput.iteration - 1], 2) == this.inputs[i].value) { ctx.fillStyle = 'green'; } else { ctx.fillStyle = 'red'; } + fillText(ctx, dec2bin(this.inputs[i].value, this.inputs[i].bitWidth), xx + this.rightDimensionX / 2, 30 + i * 20 + yy + 4, 10); + ctx.fill(); + } else { + ctx.beginPath(); + if (this.testBenchInput.testData.outputs[i].values[this.testBenchInput.iteration - 1] == 'x') { ctx.fillStyle = 'green'; } else { ctx.fillStyle = 'red'; } + fillText(ctx, 'X', xx + this.rightDimensionX / 2, 30 + i * 20 + yy + 4, 10); + ctx.fill(); + } + } + } + } + } +} + +TB_Output.prototype.tooltipText = 'Test Bench Output Selected'; +TB_Output.prototype.helplink = 'https://docs.circuitverse.org/#/testbench'; +TB_Output.prototype.centerElement = true; +TB_Output.prototype.mutableProperties = { + identifier: { + name: 'TestBench Name:', + type: 'text', + maxlength: '10', + func: 'setIdentifier', + }, +}; +TB_Output.prototype.objectType = 'TB_Output'; diff --git a/src/utils.js b/src/utils.js index 138da3d..76054e7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,103 +1,82 @@ -import { Scope } from "./circuit"; -import { dots } from "./canvasApi"; -import { showProperties } from "./ux"; - -window.scopeList = {} -window.globalScope = undefined -window.unit = 10; // size of each division/ not used everywhere, to be deprecated -window.uniqueIdCounter = 10; // size of each division/ not used everywhere, to be deprecated -window.embed = false -window.wireToBeChecked = 0; // when node disconnects from another node -window.willBeUpdated = false; // scheduleUpdate() will be called if true -window.objectSelection = false; // Flag for object selection -window.errorDetected = false; // Flag for error detection - -window.prevErrorMessage = undefined; // Global variable for error messages -window.prevShowMessage = undefined; // Global variable for error messages - -window.updatePosition = true; // Flag for updating position -window.updateSimulation = true; // Flag for updating simulation -window.updateCanvas = true; // Flag for rendering - -window.gridUpdate = true; // Flag for updating grid -window.updateSubcircuit = true; // Flag for updating subCircuits - -window.loading = false; // Flag - all assets are loaded - -window.DPR = 1; // devicePixelRatio, 2 for retina displays, 1 for low resolution displays - -window.projectSaved = true; // Flag for project saved or not -window.canvasMessageData = undefined; // Globally set in draw fn () +import simulationArea from './simulationArea'; +import { + scheduleUpdate, play, updateCanvasSet, errorDetectedSet, errorDetectedGet, +} from './engine'; +window.globalScope = undefined; window.lightMode = false; // To be deprecated +window.projectId = undefined; +window.id = undefined; +window.loading = false; // Flag - all assets are loaded -window.layoutMode = false; // Flag for mode - -window.forceResetNodes = true; // FLag to reset all Nodes - - +let prevErrorMessage; // Global variable for error messages +let prevShowMessage; // Global variable for error messages export function generateId() { - var id = ""; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let id = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (var i = 0; i < 20; i++) - id += possible.charAt(Math.floor(Math.random() * possible.length)); + for (let i = 0; i < 20; i++) { id += possible.charAt(Math.floor(Math.random() * possible.length)); } return id; } -export function newCircuit(name, id) { - name = name || prompt("Enter circuit name:"); - name = stripTags(name); - if (!name) return; - const scope = new Scope(name); - if (id) scope.id = id; - scopeList[scope.id] = scope; - window.globalScope = scope; - - $('.circuits').removeClass("current"); - $('#tabsBar').append("
" + name + "
"); - $('.circuits').click(function () { - switchCircuit(this.id) - }); - if (!embed) { - showProperties(scope.root); - } - - dots(true, false); - - return scope; -} - // To strip tags from input -export function stripTags(string = "") { - return string.replace(/(<([^>]+)>)/ig, '').trim(); +export function stripTags(string = '') { + console.log(string) + return string.toString().replace(/(<([^>]+)>)/ig, '').trim(); } export function clockTick() { if (!simulationArea.clockEnabled) return; - if (errorDetected) return; - updateCanvas = true; + if (errorDetectedGet()) return; + updateCanvasSet(true); globalScope.clockTick(); play(); scheduleUpdate(0, 20); +} +/** + * Helper function to show error + * @param {string} error -The error to be shown + * @category utils + */ +export function showError(error) { + errorDetectedSet(true); + // if error ha been shown return + if (error === prevErrorMessage) return; + prevErrorMessage = error; + const id = Math.floor(Math.random() * 10000); + $('#MessageDiv').append(``); + setTimeout(() => { + prevErrorMessage = undefined; + $(`#${id}`).fadeOut(); + }, 1500); } + // Helper function to show message export function showMessage(mes) { - if (mes == prevShowMessage) return; - prevShowMessage = mes - var id = Math.floor(Math.random() * 10000); - $('#MessageDiv').append(""); - setTimeout(function () { + if (mes === prevShowMessage) return; + prevShowMessage = mes; + const id = Math.floor(Math.random() * 10000); + $('#MessageDiv').append(``); + setTimeout(() => { prevShowMessage = undefined; - $('#' + id).fadeOut() + $(`#${id}`).fadeOut(); }, 2500); } export function distance(x1, y1, x2, y2) { - return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2)); + return Math.sqrt((x2 - x1) ** 2) + ((y2 - y1) ** 2); } - +/** + * Helper function to return unique list + * @param {Array} a - any array + * @category utils + */ +export function uniq(a) { + const seen = {}; + const tmp = a.filter((item) => (seen.hasOwnProperty(item) ? false : (seen[item] = true))); + return tmp; +} diff --git a/src/ux.js b/src/ux.js index c186305..13ea0ca 100644 --- a/src/ux.js +++ b/src/ux.js @@ -1,23 +1,51 @@ -// import { AndGate } from "./module"; -// var Turbolinks = require("turbolinks"); -// Turbolinks.start(); -import { width } from "./circuit" -import { scheduleUpdate } from "./engine" -import { simulationArea } from "./simulationArea"; -// import { moduleProperty } from "./module" -window.smartDropXX = 50; -window.smartDropYY = 80; - -// Object stores the position of context menu -var ctxPos = { +/* eslint-disable import/no-cycle */ +/* eslint-disable guard-for-in */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable guard-for-in */ +import { layoutModeGet } from './layoutMode'; +import { + scheduleUpdate, wireToBeCheckedSet, updateCanvasSet, update, updateSimulationSet, +} from './engine'; +import simulationArea from './simulationArea'; +import logixFunction from './data'; +import { newCircuit, circuitProperty } from './circuit'; +import modules from './modules'; +import { updateRestrictedElementsInScope } from './restrictedElementDiv'; +import { paste } from './events'; + +export const uxvar = { + smartDropXX: 50, + smartDropYY: 80, +}; +/** + * @type {number} - Is used to calculate the position where an element from sidebar is dropped + * @category ux + */ +uxvar.smartDropXX = 50; + +/** + * @type {number} - Is used to calculate the position where an element from sidebar is dropped + * @category ux + */ +uxvar.smartDropYY = 80; + +/** + * @type {Object} - Object stores the position of context menu; + * @category ux + */ +const ctxPos = { x: 0, y: 0, visible: false, }; -// Function hides the context menu +/** + * Function hides the context menu + * @category ux + */ function hideContextMenu() { - var el = document.getElementById('contextMenu'); + const el = document.getElementById('contextMenu'); el.style = 'opacity:0;'; setTimeout(() => { el.style = 'visibility:hidden;'; @@ -25,9 +53,12 @@ function hideContextMenu() { }, 200); // Hide after 2 sec } -// Function displays context menu +/** + * Function displays context menu + * @category ux + */ function showContextMenu() { - if (layoutMode) return false; // Hide context menu when it is in Layout Mode + if (layoutModeGet()) return false; // Hide context menu when it is in Layout Mode $('#contextMenu').css({ visibility: 'visible', opacity: 1, @@ -38,11 +69,13 @@ function showContextMenu() { return false; } -// Function is called when context item is clicked -// eslint-disable-next-line no-unused-vars +/** + * Function is called when context item is clicked + * @param {number} id - id of the optoin selected + * @category ux + */ function menuItemClicked(id) { hideContextMenu(); - if (id === 0) { document.execCommand('copy'); } else if (id === 1) { @@ -51,21 +84,27 @@ function menuItemClicked(id) { // document.execCommand('paste'); it is restricted to sove this problem we use dataPasted variable paste(localStorage.getItem('clipboardData')); } else if (id === 3) { - delete_selected(); + deleteSelected(); } else if (id === 4) { undo(); undo(); } else if (id === 5) { newCircuit(); } else if (id === 6) { - createSubCircuitPrompt(); + logixFunction.createSubCircuitPrompt(); } else if (id === 7) { globalScope.centerFocus(false); } } +window.menuItemClicked = menuItemClicked; +/** + * adds some UI elements to side bar and + * menu also attaches listeners to sidebar + * @category ux + */ export function setupUI() { - var ctxEl = document.getElementById('contextMenu'); + const ctxEl = document.getElementById('contextMenu'); document.addEventListener('mousedown', (e) => { // Check if mouse is not inside the context menu and menu is visible if (!((e.clientX >= ctxPos.x && e.clientX <= ctxPos.x + ctxEl.offsetWidth) @@ -80,14 +119,14 @@ export function setupUI() { }); document.getElementById('canvasArea').oncontextmenu = showContextMenu; - $("#sideBar").resizable({ + $('#sideBar').resizable({ handles: 'e', // minWidth:270, }); - $("#menu").accordion({ + $('#menu').accordion({ collapsible: true, active: false, - heightStyle: "content" + heightStyle: 'content', }); // $( "#plot" ).resizable({ // handles: 'n', @@ -95,37 +134,36 @@ export function setupUI() { // }); $('.logixModules').mousedown(function () { - //////console.log(smartDropXX,smartDropYY); + // ////console.log(uxvar.smartDropXX,uxvar.smartDropYY); if (simulationArea.lastSelected && simulationArea.lastSelected.newElement) simulationArea.lastSelected.delete(); - console.log(this.id) - var obj = new window[this.id](); //(simulationArea.mouseX,simulationArea.mouseY); + const obj = new modules[this.id](); // (simulationArea.mouseX,simulationArea.mouseY); + // obj = new modules[this.id](); // (simulationArea.mouseX,simulationArea.mouseY); simulationArea.lastSelected = obj; // simulationArea.lastSelected=obj; // simulationArea.mouseDown=true; - smartDropXX += 70; - if (smartDropXX / globalScope.scale > width) { - smartDropXX = 50; - smartDropYY += 80; + uxvar.smartDropXX += 70; + if (uxvar.smartDropXX / globalScope.scale > width) { + uxvar.smartDropXX = 50; + uxvar.smartDropYY += 80; } }); $('.logixButton').click(function () { - logixFundtion[this.id](); + logixFunction[this.id](); }); - // var dummyCounter=0; - + // let dummyCounter=0; - // $('.logixModules').hover(function () { - // // Tooltip can be statically defined in the prototype. - // var tooltipText = window[this.id].prototype.tooltipText; - // if (!tooltipText) return; - // $("#Help").addClass("show"); - // $("#Help").empty(); - // ////console.log("SHOWING") - // $("#Help").append(tooltipText); - // }); // code goes in document ready fn only - $('.logixModules').mouseleave(function () { - $("#Help").removeClass("show"); + $('.logixModules').hover(function () { + // Tooltip can be statically defined in the prototype. + const { tooltipText } = modules[this.id].prototype; + if (!tooltipText) return; + $('#Help').addClass('show'); + $('#Help').empty(); + // //console.log("SHOWING") + $('#Help').append(tooltipText); + }); // code goes in document ready fn only + $('.logixModules').mouseleave(() => { + $('#Help').removeClass('show'); }); // code goes in document ready fn only @@ -136,152 +174,233 @@ export function setupUI() { // Save(); // }); // $('#moduleProperty').draggable(); - } +/** + * Keeps in check which property is being displayed + * @category ux + */ +let prevPropertyObj; -window.prevPropertyObj = undefined; +export function prevPropertyObjSet(param) { + prevPropertyObj = param; +} + +export function prevPropertyObjGet() { + return prevPropertyObj; +} +/** + * show properties of an object. + * @param {CircuiElement} obj - the object whose properties we want to be shown in sidebar + * @category ux + */ export function showProperties(obj) { - if (obj == prevPropertyObj) return; + // console.log(obj) + if (obj === prevPropertyObjGet()) return; hideProperties(); - - prevPropertyObj = obj; - if (simulationArea.lastSelected === undefined || simulationArea.lastSelected.objectType == "Wire" || simulationArea.lastSelected.objectType == "CircuitElement" || simulationArea.lastSelected.objectType == "Node") { + prevPropertyObjSet(obj); + if (simulationArea.lastSelected === undefined || ['Wire', 'CircuitElement', 'Node'].indexOf(simulationArea.lastSelected.objectType) !== -1) { $('#moduleProperty').show(); - $('#moduleProperty-inner').append("
" + "Project Properties" + "
"); - $('#moduleProperty-inner').append("

Project :

"); - $('#moduleProperty-inner').append("

Circuit :

"); - $('#moduleProperty-inner').append("

Clock Time : ms

"); - $('#moduleProperty-inner').append("

Clock Enabled :

"); - $('#moduleProperty-inner').append("

Lite Mode :

"); + $('#moduleProperty-inner').append("
" + 'Project Properties' + '
'); + $('#moduleProperty-inner').append(`

Project :

`); + $('#moduleProperty-inner').append(`

Circuit :

`); + $('#moduleProperty-inner').append(`

Clock Time : ms

`); + $('#moduleProperty-inner').append(`

Clock Enabled :

`); + $('#moduleProperty-inner').append(`

Lite Mode :

`); // $('#moduleProperty-inner').append("

"); - $('#moduleProperty-inner').append("

"); + $('#moduleProperty-inner').append("

"); } else { - console.log("ls:",simulationArea.lastSelected) $('#moduleProperty').show(); - $('#moduleProperty-inner').append("
" + obj.objectType + "
"); + $('#moduleProperty-inner').append(`
${obj.objectType}
`); // $('#moduleProperty').append(""); - if (!obj.fixedBitWidth) - $('#moduleProperty-inner').append("

BitWidth:

"); - - if (obj.changeInputSize) - $('#moduleProperty-inner').append("

Input Size:

"); + if (!obj.fixedBitWidth) { $('#moduleProperty-inner').append(`

BitWidth:

`); } - if (!obj.propagationDelayFixed) - $('#moduleProperty-inner').append("

Delay:

"); + if (obj.changeInputSize) { $('#moduleProperty-inner').append(`

Input Size:

`); } + if (!obj.propagationDelayFixed) { $('#moduleProperty-inner').append(`

Delay:

`); } - $('#moduleProperty-inner').append("

Label:

"); + $('#moduleProperty-inner').append(`

Label:

`); + let s; if (!obj.labelDirectionFixed) { - var s = $(""); + s = $(`${"'); s.val(obj.labelDirection); - $('#moduleProperty-inner').append("

Label Direction: " + $(s).prop('outerHTML') + "

"); + $('#moduleProperty-inner').append(`

Label Direction: ${$(s).prop('outerHTML')}

`); } if (!obj.directionFixed) { - var s = $(""); - $('#moduleProperty-inner').append("

Direction: " + $(s).prop('outerHTML') + "

"); - + s = $(`${"'); + $('#moduleProperty-inner').append(`

Direction: ${$(s).prop('outerHTML')}

`); } else if (!obj.orientationFixed) { - var s = $(""); - $('#moduleProperty-inner').append("

Orientation: " + $(s).prop('outerHTML') + "

"); + s = $(`${"'); + $('#moduleProperty-inner').append(`

Orientation: ${$(s).prop('outerHTML')}

`); } if (obj.mutableProperties) { - for (attr in obj.mutableProperties) { - var prop = obj.mutableProperties[attr]; - if (obj.mutableProperties[attr].type == "number") { - var s = "

" + prop.name + "

"; + for (const attr in obj.mutableProperties) { + const prop = obj.mutableProperties[attr]; + if (obj.mutableProperties[attr].type === 'number') { + s = `

${prop.name}

`; $('#moduleProperty-inner').append(s); - } - else if (obj.mutableProperties[attr].type == "text") { - var s = "

" + prop.name + "

"; + } else if (obj.mutableProperties[attr].type === 'text') { + s = `

${prop.name}

`; $('#moduleProperty-inner').append(s); - } - else if (obj.mutableProperties[attr].type == "button") { - var s = "

"; + } else if (obj.mutableProperties[attr].type === 'button') { + s = `

`; $('#moduleProperty-inner').append(s); } - } } } - // Tooltip can be defined in the prototype or the object itself, in addition to help map. - var tooltipText = obj && (obj.tooltipText); - if (tooltipText) { - $('#moduleProperty-inner').append('

'); - $('#toolTipButton').hover(function () { - $("#Help").addClass("show"); - $("#Help").empty(); - ////console.log("SHOWING") - $("#Help").append(tooltipText); - }); // code goes in document ready fn only - $('#toolTipButton').mouseleave(function () { - $("#Help").removeClass("show"); + const helplink = obj && (obj.helplink); + if (helplink) { + $('#moduleProperty-inner').append('

'); + $('#HelpButton').click(() => { + window.open(helplink); }); } + function checkValidBitWidth() { + const selector = $("[name='newBitWidth']"); + if (selector === undefined + || selector.val() > 32 + || selector.val() < 1 + || !$.isNumeric(selector.val())) { + // fallback to previously saves state + selector.val(selector.attr('old-val')); + } else { + selector.attr('old-val', selector.val()); + } + } - - - - - - $(".objectPropertyAttribute").on("change keyup paste click", function () { + $('.objectPropertyAttribute').on('change keyup paste click', function () { // return; - //////console.log(this.name+":"+this.value); - - + // ////console.log(this.name+":"+this.value); + checkValidBitWidth(); scheduleUpdate(); - updateCanvas = true; - wireToBeChecked = 1; - console.log("module prop",simulationArea.lastSelected) - if (simulationArea.lastSelected && simulationArea.lastSelected[this.name]){ - prevPropertyObj = simulationArea.lastSelected[this.name](this.value) || prevPropertyObj; - simulationArea.lastSelected = prevPropertyObj + updateCanvasSet(true); + wireToBeCheckedSet(1); + if (simulationArea.lastSelected && simulationArea.lastSelected[this.name]) { + prevPropertyObjSet(simulationArea.lastSelected[this.name](this.value) || prevPropertyObjGet()); + } else { + circuitProperty[this.name](this.value); } - // else{ - // simulationArea.lastSelected = moduleProperty[this.name](this.value); - // } - }) - $(".objectPropertyAttributeChecked").on("change keyup paste click", function () { + }); + $('.objectPropertyAttributeChecked').on('change keyup paste click', function () { // return; - //////console.log(this.name+":"+this.value); - - - + // console.log(this.name+":"+this.value); scheduleUpdate(); - updateCanvas = true; - wireToBeChecked = 1; - if (simulationArea.lastSelected && simulationArea.lastSelected[this.name]){ - prevPropertyObj = simulationArea.lastSelected[this.name](this.value) || prevPropertyObj; - simulationArea.lastSelected = prevPropertyObj + updateCanvasSet(true); + wireToBeCheckedSet(1); + if (simulationArea.lastSelected && simulationArea.lastSelected[this.name]) { + prevPropertyObjSet(simulationArea.lastSelected[this.name](this.value) || prevPropertyObjGet()); + } else { + circuitProperty[this.name](this.checked); } - // else{ - // moduleProperty[this.name](this.checked); - // } - }) + }); } - +/** + * Hides the properties in sidebar. + * @category ux + */ export function hideProperties() { $('#moduleProperty-inner').empty(); $('#moduleProperty').hide(); - prevPropertyObj = undefined; - $(".objectPropertyAttribute").unbind("change keyup paste click"); + prevPropertyObjSet(undefined); + $('.objectPropertyAttribute').unbind('change keyup paste click'); } - +/** + * checkss the input is safe or not + * @param {HTML} unsafe - the html which we wants to escape + * @category ux + */ function escapeHtml(unsafe) { return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} \ No newline at end of file + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +export function deleteSelected() { + $('input').blur(); + if (simulationArea.lastSelected && !(simulationArea.lastSelected.objectType === 'Node' && simulationArea.lastSelected.type !== 2)) simulationArea.lastSelected.delete(); + for (let i = 0; i < simulationArea.multipleObjectSelections.length; i++) { + if (!(simulationArea.multipleObjectSelections[i].objectType === 'Node' && simulationArea.multipleObjectSelections[i].type !== 2)) simulationArea.multipleObjectSelections[i].cleanDelete(); + } + hideProperties(); + simulationArea.multipleObjectSelections = []; + + // Updated restricted elements + updateCanvasSet(true); + scheduleUpdate(); + updateRestrictedElementsInScope(); +} + +$('#bitconverterprompt').append(` +


+


+


+


+`); +/** + * listener for opening the prompt for bin conversion + * @category ux + */ +$('#bitconverter').click(() => { + $('#bitconverterprompt').dialog({ + buttons: [ + { + text: 'Reset', + click() { + $('#decimalInput').val('0'); + $('#binaryInput').val('0'); + $('#octalInput').val('0'); + $('#hexInput').val('0'); + }, + }, + ], + }); +}); + +// convertors +const convertors = { + dec2bin: (x) => `0b${x.toString(2)}`, + dec2hex: (x) => `0x${x.toString(16)}`, + dec2octal: (x) => `0${x.toString(8)}`, +}; + +function setBaseValues(x) { + if (isNaN(x)) return; + $('#binaryInput').val(convertors.dec2bin(x)); + $('#octalInput').val(convertors.dec2octal(x)); + $('#hexInput').val(convertors.dec2hex(x)); + $('#decimalInput').val(x); +} + +$('#decimalInput').on('keyup', () => { + const x = parseInt($('#decimalInput').val(), 10); + setBaseValues(x); +}); + +$('#binaryInput').on('keyup', () => { + const x = parseInt($('#binaryInput').val(), 2); + setBaseValues(x); +}); + +$('#hexInput').on('keyup', () => { + const x = parseInt($('#hexInput').val(), 16); + setBaseValues(x); +}); + +$('#octalInput').on('keyup', () => { + const x = parseInt($('#octalInput').val(), 8); + setBaseValues(x); +}); diff --git a/src/wire.js b/src/wire.js index ebccf14..fdc4028 100644 --- a/src/wire.js +++ b/src/wire.js @@ -1,35 +1,40 @@ -//wire object -import { drawCircle, drawLine } from "./canvasApi"; -import { simulationArea } from "./simulationArea"; -import { distance } from "./utils"; -import { renderCanvas, scheduleUpdate } from "./engine"; - +/* eslint-disable no-multi-assign */ +// wire object +import { drawLine } from './canvasApi'; +import simulationArea from './simulationArea'; +import Node from './node'; +import { updateSimulationSet, forceResetNodesSet } from './engine'; + +/** + * Wire - To connect two nodes. + * @class + * @memberof module:wire + * @param {Node} node1 + * @param {Node} node2 + * @param {Scope} scope - The circuit in which wire has to be drawn + * @category wire + */ export default class Wire { constructor(node1, node2, scope) { - - this.objectType = "Wire"; + this.objectType = 'Wire'; this.node1 = node1; this.scope = scope; this.node2 = node2; - this.type = "horizontal"; - + this.type = 'horizontal'; this.updateData(); this.scope.wires.push(this); - forceResetNodes = true; - - + forceResetNodesSet(true); } - //if data changes + // if data changes updateData() { - this.x1 = this.node1.absX(); this.y1 = this.node1.absY(); this.x2 = this.node2.absX(); this.y2 = this.node2.absY(); - if (this.x1 == this.x2) this.type = "vertical"; + if (this.x1 === this.x2) this.type = 'vertical'; } updateScope(scope) { @@ -37,77 +42,75 @@ export default class Wire { this.checkConnections(); } - //to check if nodes are disconnected + // to check if nodes are disconnected checkConnections() { - var check = this.node1.deleted || this.node2.deleted || !this.node1.connections.contains(this.node2) || !this.node2.connections.contains(this.node1); + const check = this.node1.deleted || this.node2.deleted || !this.node1.connections.contains(this.node2) || !this.node2.connections.contains(this.node1); if (check) this.delete(); return check; } update() { + let updated = false; + if (embed) return updated; - if (embed) return; - - if (this.node1.absX() == this.node2.absX()) { + if (this.node1.absX() === this.node2.absX()) { this.x1 = this.x2 = this.node1.absX(); - this.type = "vertical"; - } - else if (this.node1.absY() == this.node2.absY()) { + this.type = 'vertical'; + } else if (this.node1.absY() === this.node2.absY()) { this.y1 = this.y2 = this.node1.absY(); - this.type = "horizontal"; + this.type = 'horizontal'; } - var updated = false; - if (wireToBeChecked && this.checkConnections()) { - this.delete(); - return; - } // SLOW , REMOVE - if (simulationArea.shiftDown == false && simulationArea.mouseDown == true && simulationArea.selected == false && this.checkWithin(simulationArea.mouseDownX, simulationArea.mouseDownY)) { + // if (wireToBeChecked && this.checkConnections()) { + // this.delete(); + // return updated; + // } // SLOW , REMOVE + if (simulationArea.shiftDown === false && simulationArea.mouseDown === true && simulationArea.selected === false && this.checkWithin(simulationArea.mouseDownX, simulationArea.mouseDownY)) { simulationArea.selected = true; simulationArea.lastSelected = this; updated = true; - } - else if (simulationArea.mouseDown && simulationArea.lastSelected == this && !this.checkWithin(simulationArea.mouseX, simulationArea.mouseY)) { - var n = new Node(simulationArea.mouseDownX, simulationArea.mouseDownY, 2, this.scope.root); + } else if (simulationArea.mouseDown && simulationArea.lastSelected === this && !this.checkWithin(simulationArea.mouseX, simulationArea.mouseY)) { + const n = new Node(simulationArea.mouseDownX, simulationArea.mouseDownY, 2, this.scope.root); n.clicked = true; n.wasClicked = true; simulationArea.lastSelected = n; this.converge(n); } - if (simulationArea.lastSelected == this) { + // eslint-disable-next-line no-empty + if (simulationArea.lastSelected === this) { } if (this.node1.deleted || this.node2.deleted) { this.delete(); - return; - } //if either of the nodes are deleted - - if (simulationArea.mouseDown == false) { - if (this.type == "horizontal") { - if (this.node1.absY() != this.y1) { + return updated; + } // if either of the nodes are deleted + let n; + if (simulationArea.mouseDown === false) { + if (this.type === 'horizontal') { + if (this.node1.absY() !== this.y1) { // if(this.checkConnections()){this.delete();return;} - var n = new Node(this.node1.absX(), this.y1, 2, this.scope.root); + n = new Node(this.node1.absX(), this.y1, 2, this.scope.root); this.converge(n); updated = true; - } else if (this.node2.absY() != this.y2) { + } else if (this.node2.absY() !== this.y2) { // if(this.checkConnections()){this.delete();return;} - var n = new Node(this.node2.absX(), this.y2, 2, this.scope.root); + n = new Node(this.node2.absX(), this.y2, 2, this.scope.root); this.converge(n); updated = true; } - } else if (this.type == "vertical") { - if (this.node1.absX() != this.x1) { + } else if (this.type === 'vertical') { + if (this.node1.absX() !== this.x1) { // if(this.checkConnections()){this.delete();return;} - var n = new Node(this.x1, this.node1.absY(), 2, this.scope.root); + n = new Node(this.x1, this.node1.absY(), 2, this.scope.root); this.converge(n); updated = true; - } else if (this.node2.absX() != this.x2) { + } else if (this.node2.absX() !== this.x2) { // if(this.checkConnections()){this.delete();return;} - var n = new Node(this.x2, this.node2.absY(), 2, this.scope.root); + n = new Node(this.x2, this.node2.absY(), 2, this.scope.root); this.converge(n); updated = true; } @@ -117,22 +120,13 @@ export default class Wire { } draw() { - // for calculating min-max Width,min-max Height - let ctx = simulationArea.context; - - var color; - if (simulationArea.lastSelected == this) - color = "blue"; - else if (this.node1.value == undefined || this.node2.value == undefined) - color = "red"; - else if (this.node1.bitWidth == 1) - color = ["red", "DarkGreen", "Lime"][this.node1.value + 1]; - else - color = "black"; - drawLine(ctx, this.node1.absX(), this.node1.absY(), this.node2.absX(), this.node2.absY(), color, 3); + const ctx = simulationArea.context; + let color; + if (simulationArea.lastSelected === this) { color = 'blue'; } else if (this.node1.value === undefined || this.node2.value === undefined) { color = 'red'; } else if (this.node1.bitWidth === 1) { color = ['red', 'DarkGreen', 'Lime'][this.node1.value + 1]; } else { color = 'black'; } + drawLine(ctx, this.node1.absX(), this.node1.absY(), this.node2.absX(), this.node2.absY(), color, 3); } // checks if node lies on wire @@ -142,19 +136,14 @@ export default class Wire { // fn checks if coordinate lies on wire checkWithin(x, y) { - if ((this.type == "horizontal") && (this.node1.absX() < this.node2.absX()) && (x > this.node1.absX()) && (x < this.node2.absX()) && (y === this.node2.absY())) - return true; - else if ((this.type == "horizontal") && (this.node1.absX() > this.node2.absX()) && (x < this.node1.absX()) && (x > this.node2.absX()) && (y === this.node2.absY())) - return true; - else if ((this.type == 'vertical') && (this.node1.absY() < this.node2.absY()) && (y > this.node1.absY()) && (y < this.node2.absY()) && (x === this.node2.absX())) - return true; - else if ((this.type == 'vertical') && (this.node1.absY() > this.node2.absY()) && (y < this.node1.absY()) && (y > this.node2.absY()) && (x === this.node2.absX())) - return true + if ((this.type === 'horizontal') && (this.node1.absX() < this.node2.absX()) && (x > this.node1.absX()) && (x < this.node2.absX()) && (y === this.node2.absY())) return true; + if ((this.type === 'horizontal') && (this.node1.absX() > this.node2.absX()) && (x < this.node1.absX()) && (x > this.node2.absX()) && (y === this.node2.absY())) return true; + if ((this.type === 'vertical') && (this.node1.absY() < this.node2.absY()) && (y > this.node1.absY()) && (y < this.node2.absY()) && (x === this.node2.absX())) return true; + if ((this.type === 'vertical') && (this.node1.absY() > this.node2.absY()) && (y < this.node1.absY()) && (y > this.node2.absY()) && (x === this.node2.absX())) return true; return false; - } - //add intermediate node between these 2 nodes + // add intermediate node between these 2 nodes converge(n) { this.node1.connect(n); this.node2.connect(n); @@ -162,8 +151,8 @@ export default class Wire { } delete() { - forceResetNodes = true; - updateSimulation = true; + forceResetNodesSet(true); + updateSimulationSet(true); this.node1.connections.clean(this.node2); this.node2.connections.clean(this.node1); this.scope.wires.clean(this); diff --git a/test/examples/fulladder.json b/test/examples/fulladder.json new file mode 100644 index 0000000..b73995d --- /dev/null +++ b/test/examples/fulladder.json @@ -0,0 +1,788 @@ +{ + "name": "Full Adder", + "timePeriod": 500, + "clockEnabled": true, + "projectId": "SinXcY4JI3fbD8TmQOoV", + "focussedCircuit": 24569722303, + "scopes": [ + { + "layout": { + "width": 100, + "height": 50, + "title_x": 50, + "title_y": 13, + "titleEnabled": true + }, + "allNodes": [ + { + "x": 10, + "y": 0, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 9 + ] + }, + { + "x": -10, + "y": -10, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 9 + ] + }, + { + "x": -10, + "y": 10, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 5 + ] + }, + { + "x": 20, + "y": 0, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 12 + ] + }, + { + "x": 10, + "y": 0, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 11 + ] + }, + { + "x": 220, + "y": 270, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 2, + 11 + ] + }, + { + "x": -20, + "y": -10, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 10 + ] + }, + { + "x": -20, + "y": 10, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 11 + ] + }, + { + "x": 20, + "y": 0, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 13 + ] + }, + { + "x": 230, + "y": 250, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 0, + 1, + 10 + ] + }, + { + "x": 230, + "y": 310, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 6, + 9 + ] + }, + { + "x": 220, + "y": 330, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 4, + 5, + 7 + ] + }, + { + "x": 10, + "y": 0, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 3 + ] + }, + { + "x": 10, + "y": 0, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 8 + ] + } + ], + "id": 49772494194, + "name": "Half Adder", + "Input": [ + { + "x": 190, + "y": 250, + "objectType": "Input", + "label": "A", + "direction": "RIGHT", + "labelDirection": "LEFT", + "propagationDelay": 0, + "customData": { + "nodes": { + "output1": 0 + }, + "values": { + "state": 1 + }, + "constructorParamaters": [ + "RIGHT", + 1, + { + "x": 0, + "y": 20, + "id": "9eMxrP4dCBQLPARd3R0D" + } + ] + } + }, + { + "x": 190, + "y": 330, + "objectType": "Input", + "label": "B", + "direction": "RIGHT", + "labelDirection": "LEFT", + "propagationDelay": 0, + "customData": { + "nodes": { + "output1": 4 + }, + "values": { + "state": 1 + }, + "constructorParamaters": [ + "RIGHT", + 1, + { + "x": 0, + "y": 40, + "id": "6LVvN0D4yuQqTjUHn475" + } + ] + } + } + ], + "Output": [ + { + "x": 300, + "y": 260, + "objectType": "Output", + "label": "Cout", + "direction": "LEFT", + "labelDirection": "RIGHT", + "propagationDelay": 0, + "customData": { + "nodes": { + "inp1": 12 + }, + "constructorParamaters": [ + "LEFT", + 1, + { + "x": 100, + "y": 20, + "id": "UyKOpoVFJXl3n5mmohI2" + } + ] + } + }, + { + "x": 300, + "y": 320, + "objectType": "Output", + "label": "Sum", + "direction": "LEFT", + "labelDirection": "RIGHT", + "propagationDelay": 0, + "customData": { + "nodes": { + "inp1": 13 + }, + "constructorParamaters": [ + "LEFT", + 1, + { + "x": 100, + "y": 40, + "id": "ptMKN0apfc0rr6Z68c2Z" + } + ] + } + } + ], + "AndGate": [ + { + "x": 250, + "y": 260, + "objectType": "AndGate", + "label": "", + "direction": "RIGHT", + "labelDirection": "RIGHT", + "propagationDelay": 100, + "customData": { + "constructorParamaters": [ + "RIGHT", + 2, + 1 + ], + "nodes": { + "inp": [ + 1, + 2 + ], + "output1": 3 + } + } + } + ], + "XorGate": [ + { + "x": 260, + "y": 320, + "objectType": "XorGate", + "label": "", + "direction": "RIGHT", + "labelDirection": "RIGHT", + "propagationDelay": 100, + "customData": { + "constructorParamaters": [ + "RIGHT", + 2, + 1 + ], + "nodes": { + "inp": [ + 6, + 7 + ], + "output1": 8 + } + } + } + ], + "Text": [ + { + "x": 220, + "y": 220, + "objectType": "Text", + "label": "Half Adder", + "direction": "RIGHT", + "labelDirection": "RIGHT", + "propagationDelay": 100, + "customData": { + "constructorParamaters": [ + "Half Adder" + ] + } + } + ], + "nodes": [ + 5, + 9, + 10, + 11 + ] + }, + { + "layout": { + "width": 100, + "height": 100, + "title_x": 50, + "title_y": 13, + "titleEnabled": true + }, + "allNodes": [ + { + "x": 0, + "y": 20, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 8 + ] + }, + { + "x": 0, + "y": 40, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 9 + ] + }, + { + "x": 100, + "y": 20, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 14 + ] + }, + { + "x": 100, + "y": 40, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 4 + ] + }, + { + "x": 0, + "y": 20, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 3 + ] + }, + { + "x": 0, + "y": 40, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 12 + ] + }, + { + "x": 100, + "y": 20, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 20 + ] + }, + { + "x": 100, + "y": 40, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 13 + ] + }, + { + "x": 10, + "y": 0, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 0 + ] + }, + { + "x": 10, + "y": 0, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 1 + ] + }, + { + "x": 10, + "y": 0, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 11 + ] + }, + { + "x": 520, + "y": 410, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 10, + 12 + ] + }, + { + "x": 520, + "y": 380, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 5, + 11 + ] + }, + { + "x": 10, + "y": 0, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 7 + ] + }, + { + "x": 540, + "y": 340, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 2, + 15 + ] + }, + { + "x": 540, + "y": 320, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 14, + 16 + ] + }, + { + "x": -10, + "y": -10, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 15 + ] + }, + { + "x": -10, + "y": 10, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 19 + ] + }, + { + "x": 20, + "y": 0, + "type": 1, + "bitWidth": 1, + "label": "", + "connections": [ + 21 + ] + }, + { + "x": 670, + "y": 340, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 17, + 20 + ] + }, + { + "x": 670, + "y": 360, + "type": 2, + "bitWidth": 1, + "label": "", + "connections": [ + 6, + 19 + ] + }, + { + "x": 10, + "y": 0, + "type": 0, + "bitWidth": 1, + "label": "", + "connections": [ + 18 + ] + } + ], + "id": 24569722303, + "name": "Main", + "Input": [ + { + "x": 350, + "y": 340, + "objectType": "Input", + "label": "A", + "direction": "RIGHT", + "labelDirection": "LEFT", + "propagationDelay": 0, + "customData": { + "nodes": { + "output1": 8 + }, + "values": { + "state": 1 + }, + "constructorParamaters": [ + "RIGHT", + 1, + { + "x": 0, + "y": 80, + "id": "isjsN5wAVrXtKScjhtqr" + } + ] + } + }, + { + "x": 350, + "y": 360, + "objectType": "Input", + "label": "B", + "direction": "RIGHT", + "labelDirection": "LEFT", + "propagationDelay": 0, + "customData": { + "nodes": { + "output1": 9 + }, + "values": { + "state": 0 + }, + "constructorParamaters": [ + "RIGHT", + 1, + { + "x": 0, + "y": 20, + "id": "qQg2ym9cziVhmtHPftI4" + } + ] + } + }, + { + "x": 350, + "y": 410, + "objectType": "Input", + "label": "Cin", + "direction": "RIGHT", + "labelDirection": "LEFT", + "propagationDelay": 0, + "customData": { + "nodes": { + "output1": 10 + }, + "values": { + "state": 0 + }, + "constructorParamaters": [ + "RIGHT", + 1, + { + "x": 0, + "y": 40, + "id": "LIhLyRLobNSqDIf8Elr3" + } + ] + } + } + ], + "Output": [ + { + "x": 740, + "y": 380, + "objectType": "Output", + "label": "Sum", + "direction": "LEFT", + "labelDirection": "RIGHT", + "propagationDelay": 0, + "customData": { + "nodes": { + "inp1": 13 + }, + "constructorParamaters": [ + "LEFT", + 1, + { + "x": 100, + "y": 70, + "id": "bFUf8oF3Vbn3hDtbg83g" + } + ] + } + }, + { + "x": 740, + "y": 330, + "objectType": "Output", + "label": "Cout", + "direction": "LEFT", + "labelDirection": "RIGHT", + "propagationDelay": 0, + "customData": { + "nodes": { + "inp1": 21 + }, + "constructorParamaters": [ + "LEFT", + 1, + { + "x": 100, + "y": 20, + "id": "TwOPZvWoDzQLvSRM7M7n" + } + ] + } + } + ], + "OrGate": [ + { + "x": 700, + "y": 330, + "objectType": "OrGate", + "label": "", + "direction": "RIGHT", + "labelDirection": "RIGHT", + "propagationDelay": 100, + "customData": { + "constructorParamaters": [ + "RIGHT", + 2, + 1 + ], + "nodes": { + "inp": [ + 16, + 17 + ], + "output1": 18 + } + } + } + ], + "SubCircuit": [ + { + "x": 390, + "y": 320, + "id": "49772494194", + "inputNodes": [ + 0, + 1 + ], + "outputNodes": [ + 2, + 3 + ], + "version": "2.0" + }, + { + "x": 550, + "y": 340, + "id": "49772494194", + "inputNodes": [ + 4, + 5 + ], + "outputNodes": [ + 6, + 7 + ], + "version": "2.0" + } + ], + "Text": [ + { + "x": 510, + "y": 280, + "objectType": "Text", + "label": "Full adder from 2 half Adders", + "direction": "RIGHT", + "labelDirection": "RIGHT", + "propagationDelay": 100, + "customData": { + "constructorParamaters": [ + "Full adder from 2 half Adders" + ] + } + } + ], + "nodes": [ + 11, + 12, + 14, + 15, + 19, + 20 + ] + } + ] +} \ No newline at end of file diff --git a/test/gates/decoders.test.js b/test/gates/decoders.test.js new file mode 100644 index 0000000..4a0e558 --- /dev/null +++ b/test/gates/decoders.test.js @@ -0,0 +1,96 @@ +/** + * @jest-environment jsdom + */ +import { setup } from '../../src/setup' +setup() +import Multiplexer from '../../src/modules/Multiplexer' +import Demultiplexer from '../../src/modules/Demultiplexer' +import Input from '../../src/modules/Input' +import { update, updateSimulationSet } from '../../src/engine' + +/* + * Multiplexer test + */ +describe('Multiplexer works', () => { + /* + * basic setup + */ + const a = new Multiplexer(40, 40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const n3 = a.nodeList[2] + const n4 = a.nodeList[3] + const i1 = new Input(10, 30); + const i2 = new Input(10, 50); + const i3 = new Input(10, 50); + i1.output1.connect(n1); + n1.connect(i1.output1); + i2.output1.connect(n2); + n2.connect(i2.output1); + i3.output1.connect(n4); + n4.connect(i3.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [[0, 0], [0, 1], [1, 0], [1, 1]]; + it.each(bools)("logic works for control signal 1", (val1, val2) => { + i1.state = val1 + i2.state = val2 + i3.state = 1 + updateSimulationSet(true); + update(); + expect(n3.value).toBe(val2); + }) + it.each(bools)("logic works for control signal 0", (val1, val2) => { + i1.state = val1 + i2.state = val2 + i3.state = 0 + updateSimulationSet(true); + update(); + expect(n3.value).toBe(val1); + }) +}) + +/* + * Demultiplexer test + */ +describe('Demultiplexer works', () => { + /* + * basic setup + */ + const a = new Demultiplexer(40, 40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const n3 = a.nodeList[2] + const n4 = a.nodeList[3] + const i1 = new Input(10, 30); + const i3 = new Input(10, 50); + i1.output1.connect(n1); + n1.connect(i1.output1); + i3.output1.connect(n4); + n4.connect(i3.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [0,1]; + it.each(bools)("logic works for control signal 1", (val1) => { + i1.state = val1 + i3.state = 1 + updateSimulationSet(true); + update(); + expect([n2.value,n3.value]).toEqual([0,val1]); + }) + it.each(bools)("logic works for control signal 0", (val1) => { + i1.state = val1 + i3.state = 0 + updateSimulationSet(true); + update(); + expect([n2.value, n3.value]).toEqual([val1,0]); + }) +}) \ No newline at end of file diff --git a/test/gates/gates.test.js b/test/gates/gates.test.js new file mode 100644 index 0000000..c3a7ce2 --- /dev/null +++ b/test/gates/gates.test.js @@ -0,0 +1,305 @@ +/** + * @jest-environment jsdom + */ +import { setup } from '../../src/setup' +setup() +import AndGate from '../../src/modules/AndGate' +import OrGate from '../../src/modules/OrGate'; +import XnorGate from '../../src/modules/XnorGate'; +import XorGate from '../../src/modules/XorGate'; +import NandGate from '../../src/modules/NandGate'; +import NorGate from '../../src/modules/NorGate'; +import NotGate from '../../src/modules/NotGate'; +import Input from '../../src/modules/Input' +import { update, updateSimulationSet } from '../../src/engine' + +/* + * AndGate test + */ +describe('AndGate works', () => { + /* + * basic setup + */ + const a = new AndGate(40,40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const n3 = a.nodeList[2] + const i1 = new Input(10,30); + const i2 = new Input(10,50); + i1.output1.connect(n1); + n1.connect(i1.output1); + i2.output1.connect(n2); + n2.connect(i2.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [[0,0],[0,1],[1,0],[1,1]]; + it.each(bools)("logic works", (val1,val2) => { + i1.state = val1 + i2.state = val2 + updateSimulationSet(true); + update(); + expect(n3.value).toBe(val1&val2); + }) + it('can change input node', () => { + const x = a.changeInputSize(3) + expect(x.inp.length).toBe(3) + }); +}); + +/* + * OrGate test + */ +describe('OrGate works', () => { + /* + * basic setup + */ + const a = new OrGate(40, 40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const n3 = a.nodeList[2] + const i1 = new Input(10, 30); + const i2 = new Input(10, 50); + i1.output1.connect(n1); + n1.connect(i1.output1); + i2.output1.connect(n2); + n2.connect(i2.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [[0, 0], [0, 1], [1, 0], [1, 1]]; + it.each(bools)("logic works", (val1, val2) => { + i1.state = val1 + i2.state = val2 + updateSimulationSet(true); + update(); + expect(n3.value).toBe(val1 | val2); + }) + it('can change input node', () => { + const x = a.changeInputSize(3) + expect(x.inp.length).toBe(3) + }); +}); + +/* + * XorGate test + */ +describe('XorGate works', () => { + /* + * basic setup + */ + const a = new XorGate(40, 40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const n3 = a.nodeList[2] + const i1 = new Input(10, 30); + const i2 = new Input(10, 50); + i1.output1.connect(n1); + n1.connect(i1.output1); + i2.output1.connect(n2); + n2.connect(i2.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [[0, 0], [0, 1], [1, 0], [1, 1]]; + it.each(bools)("logic works", (val1, val2) => { + i1.state = val1 + i2.state = val2 + updateSimulationSet(true); + update(); + expect(n3.value).toBe(val1 ^ val2); + }) + it('can change input node', () => { + const x = a.changeInputSize(3) + expect(x.inp.length).toBe(3) + }); +}); + + +/* + * XorGate test + */ +describe('XorGate works', () => { + /* + * basic setup + */ + const a = new XorGate(40, 40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const n3 = a.nodeList[2] + const i1 = new Input(10, 30); + const i2 = new Input(10, 50); + i1.output1.connect(n1); + n1.connect(i1.output1); + i2.output1.connect(n2); + n2.connect(i2.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [[0, 0], [0, 1], [1, 0], [1, 1]]; + it.each(bools)("logic works", (val1, val2) => { + i1.state = val1 + i2.state = val2 + updateSimulationSet(true); + update(); + expect(n3.value).toBe(val1 ^ val2); + }) + it('can change input node', () => { + const x = a.changeInputSize(3) + expect(x.inp.length).toBe(3) + }); +}); + +/* + * XnorGate test + */ +describe('XnorGate works', () => { + /* + * basic setup + */ + const a = new XnorGate(40, 40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const n3 = a.nodeList[2] + const i1 = new Input(10, 30); + const i2 = new Input(10, 50); + i1.output1.connect(n1); + n1.connect(i1.output1); + i2.output1.connect(n2); + n2.connect(i2.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [[0, 0], [0, 1], [1, 0], [1, 1]]; + it.each(bools)("logic works", (val1, val2) => { + i1.state = val1 + i2.state = val2 + updateSimulationSet(true); + update(); + expect(n3.value).toEqual(+!(val1 ^ val2)); + }) + it('can change input node', () => { + const x = a.changeInputSize(3) + expect(x.inp.length).toBe(3) + }); +}); + + +/* + * NorGate test + */ +describe('NorGate works', () => { + /* + * basic setup + */ + const a = new NorGate(40, 40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const n3 = a.nodeList[2] + const i1 = new Input(10, 30); + const i2 = new Input(10, 50); + i1.output1.connect(n1); + n1.connect(i1.output1); + i2.output1.connect(n2); + n2.connect(i2.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [[0, 0], [0, 1], [1, 0], [1, 1]]; + it.each(bools)("logic works", (val1, val2) => { + i1.state = val1 + i2.state = val2 + updateSimulationSet(true); + update(); + expect(n3.value).toEqual(+!(val1 | val2)); + }) + it('can change input node', () => { + const x = a.changeInputSize(3) + expect(x.inp.length).toBe(3) + }); +}); + + +/* + * NandGate test + */ +describe('NandGate works', () => { + /* + * basic setup + */ + const a = new NandGate(40, 40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const n3 = a.nodeList[2] + const i1 = new Input(10, 30); + const i2 = new Input(10, 50); + i1.output1.connect(n1); + n1.connect(i1.output1); + i2.output1.connect(n2); + n2.connect(i2.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [[0, 0], [0, 1], [1, 0], [1, 1]]; + it.each(bools)("logic works", (val1, val2) => { + i1.state = val1 + i2.state = val2 + updateSimulationSet(true); + update(); + expect(n3.value).toEqual(+!(val1 & val2)); + }) + it('can change input node', () => { + const x = a.changeInputSize(3) + expect(x.inp.length).toBe(3) + }); +}); + + +/* + * NotGate test + */ +describe('NotGate works', () => { + /* + * basic setup + */ + const a = new NotGate(40, 40); + const n1 = a.nodeList[0] + const n2 = a.nodeList[1] + const i1 = new Input(10, 30); + i1.output1.connect(n1); + n1.connect(i1.output1); + /* + * test cases + */ + it("draws correctly", () => { + + }) + var bools = [0,1]; + it.each(bools)("logic works", (val1) => { + i1.state = val1 + updateSimulationSet(true); + update(); + expect(n2.value).toEqual(+!(val1)); + }) +}); diff --git a/test/gates/sequential.test.js b/test/gates/sequential.test.js new file mode 100644 index 0000000..84c630c --- /dev/null +++ b/test/gates/sequential.test.js @@ -0,0 +1,234 @@ +/** + * @jest-environment jsdom + */ +import { setup } from '../../src/setup' +setup() +import Input from '../../src/modules/Input' +import { update, updateSimulationSet } from '../../src/engine' +import Dlatch from '../../src/sequential/Dlatch'; +import DflipFlop from '../../src/sequential/DflipFlop'; +import TflipFlop from '../../src/sequential/TflipFlop'; +import SRflipFlop from '../../src/sequential/SRflipFlop'; +import JKflipFlop from '../../src/sequential/JKflipFlop'; + +/* + * Dlatch test + */ +describe('Dlatch works', () => { + /* + * basic setup + */ + const a = new Dlatch(40, 40); + const c = a.nodeList[0] + const d = a.nodeList[1] + const q = a.nodeList[2] + const i1 = new Input(10, 30); + i1.output1.connect(d); + d.connect(i1.output1); + it('draws right', () => { + i1.state = 0 + c.value = 0 + updateSimulationSet(true) + update() + c.value = 1 + updateSimulationSet(true) + update() + expect(q.value).toEqual(0) + }); + it('value of q', () => { + i1.state = 1 + c.value = 0 + updateSimulationSet(true) + update() + c.value = 1 + updateSimulationSet(true) + update() + expect(q.value).toEqual(1) + }); +}) + +/* + * DflipFlop test + */ +describe('DflipFlop works', () => { + /* + * basic setup + */ + const a = new DflipFlop(40, 40); + const c = a.nodeList[0] + const d = a.nodeList[1] + const q = a.nodeList[2] + const i1 = new Input(10, 30); + i1.output1.connect(d); + d.connect(i1.output1); + it('draws right', () => { + i1.state = 0 + c.value = 0 + updateSimulationSet(true) + update() + c.value = 1 + updateSimulationSet(true) + update() + expect(q.value).toEqual(0) + }); + it('value of q', () => { + i1.state = 1 + c.value = 0 + updateSimulationSet(true) + update() + c.value = 1 + updateSimulationSet(true) + update() + expect(q.value).toEqual(1) + }); +}) + +/* + * TflipFlop test + */ +describe('TflipFlop works', () => { + /* + * basic setup + */ + const a = new TflipFlop(40, 40); + const c = a.nodeList[0] + const t = a.nodeList[1] + const q = a.nodeList[2] + const i1 = new Input(10, 30); + i1.output1.connect(t); + t.connect(i1.output1); + it('draws right', () => { + i1.state = 0 + c.value = 0 + updateSimulationSet(true) + update() + c.value = 1 + expect(q.value).toEqual(0) + updateSimulationSet(true) + update() + expect(q.value).toEqual(0) + }); + it('value of T', () => { + i1.state = 1 + c.value = 0 + updateSimulationSet(true) + update() + expect(q.value).toEqual(0) + c.value = 1 + updateSimulationSet(true) + update() + expect(q.value).toEqual(1) + c.value = 0 + updateSimulationSet(true) + update() + expect(q.value).toEqual(1) + c.value = 1 + updateSimulationSet(true) + update() + expect(q.value).toEqual(0) + }); +}) + +/* + * SRflipFlop test + */ +describe('SRflipFlop works', () => { + /* + * basic setup + */ + const a = new SRflipFlop(40, 40); + const r = a.nodeList[0] + const s = a.nodeList[1] + const q = a.nodeList[2] + const i1 = new Input(10, 30); + const i2 = new Input(20, 40); + i1.output1.connect(s); + s.connect(i1.output1); + i2.output1.connect(r); + r.connect(i2.output1); + it('1 0 sr logic', () => { + i1.state = 1 + i2.state = 0 + updateSimulationSet(true) + update() + expect(q.value).toEqual(1) + }); + it('0 1 sr logic', () => { + i1.state = 0 + i2.state = 1 + updateSimulationSet(true) + update() + expect(q.value).toEqual(0) + }); + it('value of q with 0,0 sr logic', () => { + i1.state = 0 + i2.state = 0 + const state = q.value + updateSimulationSet(true) + update() + expect(q.value).toEqual(+state) + }); +}) + +/* + * JKflipFlop test + */ +describe('JKflipFlop works', () => { + /* + * basic setup + */ + const a = new JKflipFlop(40, 40); + const j = a.nodeList[0] + const k = a.nodeList[1] + const c = a.nodeList[2] + const q = a.nodeList[3] + const i1 = new Input(10, 30); + const i2 = new Input(10, 50); + i1.output1.connect(j); + j.connect(i1.output1); + i2.output1.connect(k); + k.connect(i2.output1); + it('0 1 jk logic', () => { + i1.state = 1 + c.value = 0 + updateSimulationSet(true) + update() + c.value = 1 + expect(q.value).toEqual(0) + updateSimulationSet(true) + update() + expect(q.value).toEqual(1) + }); + it('1 0 jk logic', () => { + i2.state = 1 + c.value = 0 + updateSimulationSet(true) + update() + c.value = 1 + expect(q.value).toEqual(1) + updateSimulationSet(true) + update() + expect(q.value).toEqual(0) + }); + it('value of q with 1,1 jk', () => { + i1.state = 1 + i2.state = 1 + c.value = 0 + const state = q.value + updateSimulationSet(true) + update() + expect(q.value).toEqual(+state) + c.value = 1 + updateSimulationSet(true) + update() + expect(q.value).toEqual(+!state) + c.value = 0 + updateSimulationSet(true) + update() + expect(q.value).toEqual(+!state) + c.value = 1 + updateSimulationSet(true) + update() + expect(q.value).toEqual(+state) + }); +}) diff --git a/test/listenertest.test.js b/test/listenertest.test.js new file mode 100644 index 0000000..554b660 --- /dev/null +++ b/test/listenertest.test.js @@ -0,0 +1,39 @@ +/** + * @jest-environment jsdom + */ + +const puppeteer = require('puppeteer') +let browser +let page + +beforeAll(async () => { + browser = await puppeteer.launch() + page = await browser.newPage() + await page.goto('file://' + process.cwd() +'/index.html') +}) + +describe('button', function () { + it("zooms on clicking button", async () => { + const prevScale = await page.evaluate(() => globalScope.scale); + await page.click('#zoomIn') + const newScale = await page.evaluate(() => globalScope.scale); + expect(newScale).toBeGreaterThan(prevScale); + },1000000) + + it("zooms on scrolling", async () => { + const prevScale = await page.evaluate(() => globalScope.scale); + await page.waitForSelector('#simulationArea'); + await page.hover('#simulationArea'); + await page.evaluate(() => { + var cEvent = new Event('mousewheel'); + cEvent.detail = -53; + document.querySelector('#simulationArea').dispatchEvent(cEvent) + }); + const newScale = await page.evaluate(() => globalScope.scale); + expect(newScale).toBeGreaterThan(prevScale); + },1000000); + + afterAll(async () => { + await browser.close() + }) +}); diff --git a/test/load.test.js b/test/load.test.js new file mode 100644 index 0000000..9c8d3cc --- /dev/null +++ b/test/load.test.js @@ -0,0 +1,16 @@ +/** + * @jest-environment jsdom + */ +import { setup } from '../src/setup' +setup() +import load from '../src/data/load' +import * as data from './examples/fulladder.json' + +jest + .dontMock('fs'); +describe('button', () => { + it("loads without error", () => { + load(data) + // expect().not.toThrow() + }); +}); \ No newline at end of file diff --git a/test/restrictedElements.test.js b/test/restrictedElements.test.js new file mode 100644 index 0000000..c9b75d3 --- /dev/null +++ b/test/restrictedElements.test.js @@ -0,0 +1,22 @@ +/** + * @jest-environment jsdom + */ +import { setup } from '../src/setup' +setup() +import AndGate from '../src/modules/AndGate' +import { updateRestrictedElementsInScope } from '../src/restrictedElementDiv' + + +jest + .dontMock('fs'); +describe('button', () => { + it("validates that an restricted element is normal at first", () => { + expect(($('#restrictedElementsDiv--list').text())).toEqual(" ") + }); + it("validates that an restricted element is shown in restricted items div when it is used", () => { + window.restrictedElements = ["AndGate"] + new AndGate(40, 40) + updateRestrictedElementsInScope() + expect(($('#restrictedElementsDiv--list').text())).toEqual("AndGate") + }); +}); diff --git a/test/sample.test.js b/test/sample.test.js deleted file mode 100644 index e4df856..0000000 --- a/test/sample.test.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @jest-environment jsdom - */ - -const fs = require('fs'); -const path = require('path'); -const html = fs.readFileSync(path.resolve(__dirname, '../index.html'), 'utf8'); - -const $ = global.jQuery = global.$ = require('jquery'); - -document.documentElement.innerHTML = html.toString(); - -require('jquery-ui-bundle'); - -import { setup } from '../src/circuit' -import { AndGate } from '../src/module' -import { Array } from '../src/arrayHelpers' -import { updateRestrictedElementsInScope } from '../src/restrictedElementDiv' -window["Array"] = Array - -jest - .dontMock('fs'); -describe('button', function () { - it("validates that an restricted element is normal at first", function () { - window.restrictedElements = [] - setup() - expect(($('#restrictedElementsDiv--list').text())).toEqual(" ") - }); - it("validates that an restricted element is shown in restricted items div when it is used", function () { - window.restrictedElements = ["AndGate"] - setup() - new AndGate(40, 40) - updateRestrictedElementsInScope() - expect(($('#restrictedElementsDiv--list').text())).toEqual("AndGate") - }); -}); diff --git a/test/save.test.js b/test/save.test.js new file mode 100644 index 0000000..b685ab4 --- /dev/null +++ b/test/save.test.js @@ -0,0 +1,20 @@ +/** + * @jest-environment jsdom + */ +import { setup } from '../src/setup' +setup() +import load from '../src/data/load' +import * as data from './examples/fulladder.json' +import Input from '../src/modules/Input' +import save from '../src/data/save'; + +jest + .dontMock('fs'); +describe('button', () => { + it("Draw right without error", () => { + load(data) + window.logix_project_id = data.projectId + new Input(10,20); + expect(save).not.toThrow() + }); +}); \ No newline at end of file diff --git a/test/subcircuit.test.js b/test/subcircuit.test.js new file mode 100644 index 0000000..d089944 --- /dev/null +++ b/test/subcircuit.test.js @@ -0,0 +1,31 @@ +/** + * @jest-environment jsdom + */ +import { setup } from '../src/setup' +setup() +import load from '../src/data/load' +import * as data from './examples/fulladder.json' +import { updateSimulationSet, update } from '../src/engine'; + +jest + .dontMock('fs'); +describe('button', () => { + it("Draw right without error", () => { + load(data) + // expect().not.toThrow() + }); + const bools = [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]] + it.each(bools)("logic works", (val1,val2,val3) => { + const i1 = globalScope.Input[0]; + const i2 = globalScope.Input[1]; + const i3 = globalScope.Input[2]; + const cout = globalScope.Output[1]; + const sum = globalScope.Output[0]; + i1.state = val1 + i2.state = val2 + i3.state = val3 + updateSimulationSet(true); + update(); + expect([cout.inp1.value, sum.inp1.value]).toEqual([(val1 & val2) | (val3 & val2) | (val1 & val3),val1^val2^val3]) + }); +}); diff --git a/webpack.config.js b/webpack.config.js index 087cd3f..cf17e4e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,5 @@ const path = require('path'); +var webpack = require('webpack'); module.exports = { entry: './src/app.js', @@ -9,6 +10,12 @@ module.exports = { devServer: { contentBase: './dist' }, + plugins: [ + new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery" + }) + ], module: { rules: [ {