From 99e916beaf6afe5b2300bd95e73267550767bf7a Mon Sep 17 00:00:00 2001 From: David Barksdale Date: Sat, 3 Dec 2016 14:23:02 -0600 Subject: Add Semantic-UI and get blockly working --- assets/blocks_compressed.js | 149 + assets/css/semantic.min.css | 11 + assets/css/themes/basic/assets/fonts/icons.eot | Bin 0 -> 40166 bytes assets/css/themes/basic/assets/fonts/icons.svg | 450 + assets/css/themes/basic/assets/fonts/icons.ttf | Bin 0 -> 39924 bytes assets/css/themes/basic/assets/fonts/icons.woff | Bin 0 -> 24676 bytes assets/css/themes/default/assets/fonts/icons.eot | Bin 0 -> 76518 bytes assets/css/themes/default/assets/fonts/icons.otf | Bin 0 -> 93888 bytes assets/css/themes/default/assets/fonts/icons.svg | 685 + assets/css/themes/default/assets/fonts/icons.ttf | Bin 0 -> 152796 bytes assets/css/themes/default/assets/fonts/icons.woff | Bin 0 -> 90412 bytes assets/css/themes/default/assets/fonts/icons.woff2 | Bin 0 -> 71896 bytes assets/css/themes/default/assets/images/flags.png | Bin 0 -> 28123 bytes .../themes/github/assets/fonts/octicons-local.ttf | Bin 0 -> 53604 bytes assets/css/themes/github/assets/fonts/octicons.svg | 200 + assets/css/themes/github/assets/fonts/octicons.ttf | Bin 0 -> 31740 bytes .../css/themes/github/assets/fonts/octicons.woff | Bin 0 -> 17772 bytes assets/css/themes/material/assets/fonts/icons.eot | Bin 0 -> 143258 bytes assets/css/themes/material/assets/fonts/icons.svg | 2373 ++ assets/css/themes/material/assets/fonts/icons.ttf | Bin 0 -> 128180 bytes assets/css/themes/material/assets/fonts/icons.woff | Bin 0 -> 57620 bytes assets/en.js | 391 + assets/media/1x1.gif | Bin 0 -> 43 bytes assets/media/click.mp3 | Bin 0 -> 2304 bytes assets/media/click.ogg | Bin 0 -> 4865 bytes assets/media/click.wav | Bin 0 -> 3782 bytes assets/media/delete.mp3 | Bin 0 -> 3123 bytes assets/media/delete.ogg | Bin 0 -> 5731 bytes assets/media/delete.wav | Bin 0 -> 9164 bytes assets/media/disconnect.mp3 | Bin 0 -> 1586 bytes assets/media/disconnect.ogg | Bin 0 -> 4404 bytes assets/media/disconnect.wav | Bin 0 -> 1492 bytes assets/media/handclosed.cur | Bin 0 -> 326 bytes assets/media/handdelete.cur | Bin 0 -> 766 bytes assets/media/handopen.cur | Bin 0 -> 198 bytes assets/media/quote0.png | Bin 0 -> 796 bytes assets/media/quote1.png | Bin 0 -> 738 bytes assets/media/sprites.png | Bin 0 -> 4146 bytes assets/media/sprites.svg | 74 + build.boot | 3 +- generate_deps.py | 57 + src/blockly/core/block.js | 1364 ++ src/blockly/core/block_render_svg.js | 969 + src/blockly/core/block_svg.js | 1629 ++ src/blockly/core/blockly.js | 453 + src/blockly/core/blocks.js | 33 + src/blockly/core/bubble.js | 579 + src/blockly/core/comment.js | 278 + src/blockly/core/connection.js | 615 + src/blockly/core/connection_db.js | 301 + src/blockly/core/constants.js | 202 + src/blockly/core/contextmenu.js | 148 + src/blockly/core/css.js | 782 + src/blockly/core/events.js | 818 + src/blockly/core/field.js | 495 + src/blockly/core/field_angle.js | 294 + src/blockly/core/field_checkbox.js | 117 + src/blockly/core/field_colour.js | 234 + src/blockly/core/field_date.js | 346 + src/blockly/core/field_dropdown.js | 320 + src/blockly/core/field_image.js | 171 + src/blockly/core/field_label.js | 104 + src/blockly/core/field_number.js | 101 + src/blockly/core/field_textinput.js | 327 + src/blockly/core/field_variable.js | 155 + src/blockly/core/flyout.js | 1364 ++ src/blockly/core/flyout_button.js | 169 + src/blockly/core/generator.js | 369 + src/blockly/core/icon.js | 203 + src/blockly/core/inject.js | 378 + src/blockly/core/input.js | 241 + src/blockly/core/msg.js | 62 + src/blockly/core/mutator.js | 389 + src/blockly/core/names.js | 143 + src/blockly/core/options.js | 231 + src/blockly/core/procedures.js | 287 + src/blockly/core/rendered_connection.js | 395 + src/blockly/core/scrollbar.js | 750 + src/blockly/core/toolbox.js | 650 + src/blockly/core/tooltip.js | 286 + src/blockly/core/trashcan.js | 332 + src/blockly/core/utils.js | 668 + src/blockly/core/variables.js | 273 + src/blockly/core/warning.js | 185 + src/blockly/core/widgetdiv.js | 152 + src/blockly/core/workspace.js | 501 + src/blockly/core/workspace_svg.js | 1401 ++ src/blockly/core/xml.js | 566 + src/blockly/core/zoom_controls.js | 239 + src/deps.cljs | 150 + src/index.cljs.hl | 30 +- src/semantic.js | 22500 +++++++++++++++++++ src/semantic.min.js | 19 + 93 files changed, 48158 insertions(+), 3 deletions(-) create mode 100644 assets/blocks_compressed.js create mode 100644 assets/css/semantic.min.css create mode 100644 assets/css/themes/basic/assets/fonts/icons.eot create mode 100644 assets/css/themes/basic/assets/fonts/icons.svg create mode 100644 assets/css/themes/basic/assets/fonts/icons.ttf create mode 100644 assets/css/themes/basic/assets/fonts/icons.woff create mode 100644 assets/css/themes/default/assets/fonts/icons.eot create mode 100644 assets/css/themes/default/assets/fonts/icons.otf create mode 100644 assets/css/themes/default/assets/fonts/icons.svg create mode 100644 assets/css/themes/default/assets/fonts/icons.ttf create mode 100644 assets/css/themes/default/assets/fonts/icons.woff create mode 100644 assets/css/themes/default/assets/fonts/icons.woff2 create mode 100644 assets/css/themes/default/assets/images/flags.png create mode 100644 assets/css/themes/github/assets/fonts/octicons-local.ttf create mode 100644 assets/css/themes/github/assets/fonts/octicons.svg create mode 100644 assets/css/themes/github/assets/fonts/octicons.ttf create mode 100644 assets/css/themes/github/assets/fonts/octicons.woff create mode 100644 assets/css/themes/material/assets/fonts/icons.eot create mode 100644 assets/css/themes/material/assets/fonts/icons.svg create mode 100644 assets/css/themes/material/assets/fonts/icons.ttf create mode 100644 assets/css/themes/material/assets/fonts/icons.woff create mode 100644 assets/en.js create mode 100644 assets/media/1x1.gif create mode 100644 assets/media/click.mp3 create mode 100644 assets/media/click.ogg create mode 100644 assets/media/click.wav create mode 100644 assets/media/delete.mp3 create mode 100644 assets/media/delete.ogg create mode 100644 assets/media/delete.wav create mode 100644 assets/media/disconnect.mp3 create mode 100644 assets/media/disconnect.ogg create mode 100644 assets/media/disconnect.wav create mode 100644 assets/media/handclosed.cur create mode 100644 assets/media/handdelete.cur create mode 100644 assets/media/handopen.cur create mode 100644 assets/media/quote0.png create mode 100644 assets/media/quote1.png create mode 100644 assets/media/sprites.png create mode 100644 assets/media/sprites.svg create mode 100644 generate_deps.py create mode 100644 src/blockly/core/block.js create mode 100644 src/blockly/core/block_render_svg.js create mode 100644 src/blockly/core/block_svg.js create mode 100644 src/blockly/core/blockly.js create mode 100644 src/blockly/core/blocks.js create mode 100644 src/blockly/core/bubble.js create mode 100644 src/blockly/core/comment.js create mode 100644 src/blockly/core/connection.js create mode 100644 src/blockly/core/connection_db.js create mode 100644 src/blockly/core/constants.js create mode 100644 src/blockly/core/contextmenu.js create mode 100644 src/blockly/core/css.js create mode 100644 src/blockly/core/events.js create mode 100644 src/blockly/core/field.js create mode 100644 src/blockly/core/field_angle.js create mode 100644 src/blockly/core/field_checkbox.js create mode 100644 src/blockly/core/field_colour.js create mode 100644 src/blockly/core/field_date.js create mode 100644 src/blockly/core/field_dropdown.js create mode 100644 src/blockly/core/field_image.js create mode 100644 src/blockly/core/field_label.js create mode 100644 src/blockly/core/field_number.js create mode 100644 src/blockly/core/field_textinput.js create mode 100644 src/blockly/core/field_variable.js create mode 100644 src/blockly/core/flyout.js create mode 100644 src/blockly/core/flyout_button.js create mode 100644 src/blockly/core/generator.js create mode 100644 src/blockly/core/icon.js create mode 100644 src/blockly/core/inject.js create mode 100644 src/blockly/core/input.js create mode 100644 src/blockly/core/msg.js create mode 100644 src/blockly/core/mutator.js create mode 100644 src/blockly/core/names.js create mode 100644 src/blockly/core/options.js create mode 100644 src/blockly/core/procedures.js create mode 100644 src/blockly/core/rendered_connection.js create mode 100644 src/blockly/core/scrollbar.js create mode 100644 src/blockly/core/toolbox.js create mode 100644 src/blockly/core/tooltip.js create mode 100644 src/blockly/core/trashcan.js create mode 100644 src/blockly/core/utils.js create mode 100644 src/blockly/core/variables.js create mode 100644 src/blockly/core/warning.js create mode 100644 src/blockly/core/widgetdiv.js create mode 100644 src/blockly/core/workspace.js create mode 100644 src/blockly/core/workspace_svg.js create mode 100644 src/blockly/core/xml.js create mode 100644 src/blockly/core/zoom_controls.js create mode 100644 src/deps.cljs create mode 100644 src/semantic.js create mode 100644 src/semantic.min.js diff --git a/assets/blocks_compressed.js b/assets/blocks_compressed.js new file mode 100644 index 0000000..17b6b12 --- /dev/null +++ b/assets/blocks_compressed.js @@ -0,0 +1,149 @@ +// Do not edit this file; automatically generated by build.py. +'use strict'; + + +// Copyright 2012 Google Inc. Apache License 2.0 +Blockly.Blocks.lists={};Blockly.Blocks.lists.HUE=260;Blockly.Blocks.lists_create_empty={init:function(){this.jsonInit({message0:Blockly.Msg.LISTS_CREATE_EMPTY_TITLE,output:"Array",colour:Blockly.Blocks.lists.HUE,tooltip:Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP,helpUrl:Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL})}}; +Blockly.Blocks.lists_create_with={init:function(){this.setHelpUrl(Blockly.Msg.LISTS_CREATE_WITH_HELPURL);this.setColour(Blockly.Blocks.lists.HUE);this.itemCount_=3;this.updateShape_();this.setOutput(!0,"Array");this.setMutator(new Blockly.Mutator(["lists_create_with_item"]));this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"), +10);this.updateShape_()},decompose:function(a){var b=a.newBlock("lists_create_with_container");b.initSvg();for(var c=b.getInput("STACK").connection,e=0;e\u200f","GT"],["\u200f\u2265\u200f","GTE"]],b=[["=","EQ"],["\u2260","NEQ"],["<","LT"],["\u2264","LTE"],[">","GT"],["\u2265","GTE"]],a=this.RTL?a:b;this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL);this.setColour(Blockly.Blocks.logic.HUE);this.setOutput(!0,"Boolean");this.appendValueInput("A");this.appendValueInput("B").appendField(new Blockly.FieldDropdown(a), +"OP");this.setInputsInline(!0);var c=this;this.setTooltip(function(){var a=c.getFieldValue("OP");return{EQ:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ,NEQ:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ,LT:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT,LTE:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE,GT:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT,GTE:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE}[a]});this.prevBlocks_=[null,null]},onchange:function(a){var b=this.getInputTargetBlock("A"),c=this.getInputTargetBlock("B");if(b&&c&&!b.outputConnection.checkType_(c.outputConnection)){Blockly.Events.setGroup(a.group); +for(a=0;ad;d++){var f=1==d?b:c;f&&!f.outputConnection.checkType_(e)&&(Blockly.Events.setGroup(a.group),e===this.prevParentConnection_?(this.unplug(),e.getSourceBlock().bumpNeighbours_()):(f.unplug(),f.bumpNeighbours_()),Blockly.Events.setGroup(!1))}this.prevParentConnection_=e}}; \ No newline at end of file diff --git a/assets/css/semantic.min.css b/assets/css/semantic.min.css new file mode 100644 index 0000000..3b91e4c --- /dev/null +++ b/assets/css/semantic.min.css @@ -0,0 +1,11 @@ + /* + * # Semantic UI - 2.2.6 + * https://github.com/Semantic-Org/Semantic-UI + * http://www.semantic-ui.com/ + * + * Copyright 2014 Contributors + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ +*,:after,:before{box-sizing:inherit}html{box-sizing:border-box;font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}input[type=text],input[type=email],input[type=search],input[type=password]{-webkit-appearance:none;-moz-appearance:none}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0;color:#4183C4;text-decoration:none}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}body,html{height:100%}html{font-size:14px}body{margin:0;padding:0;overflow-x:hidden;min-width:320px;background:#FFF;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:14px;line-height:1.4285em;color:rgba(0,0,0,.87);font-smoothing:antialiased}h1,h2,h3,h4,h5{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;line-height:1.2857em;margin:calc(2rem - .14285em) 0 1rem;font-weight:700;padding:0}h1{min-height:1rem;font-size:2rem}h2{font-size:1.714rem}h3{font-size:1.28rem}h4{font-size:1.071rem}h5{font-size:1rem}h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,p:first-child{margin-top:0}h1:last-child,h2:last-child,h3:last-child,h4:last-child,h5:last-child,p:last-child{margin-bottom:0}p{margin:0 0 1em;line-height:1.4285em}a:hover{color:#1e70bf;text-decoration:none}::-webkit-selection{background-color:#CCE2FF;color:rgba(0,0,0,.87)}::-moz-selection{background-color:#CCE2FF;color:rgba(0,0,0,.87)}::selection{background-color:#CCE2FF;color:rgba(0,0,0,.87)}input::-webkit-selection,textarea::-webkit-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.87)}input::-moz-selection,textarea::-moz-selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.87)}input::selection,textarea::selection{background-color:rgba(100,100,100,.4);color:rgba(0,0,0,.87)}.ui.button{cursor:pointer;display:inline-block;min-height:1em;outline:0;border:none;vertical-align:baseline;background:#E0E1E2;color:rgba(0,0,0,.6);font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;margin:0 .25em 0 0;padding:.78571429em 1.5em;text-transform:none;text-shadow:none;font-weight:700;line-height:1em;font-style:normal;text-align:center;text-decoration:none;border-radius:.28571429rem;box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(34,36,38,.15) inset;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease;will-change:'';-webkit-tap-highlight-color:transparent}.ui.button:hover{background-color:#CACBCD;background-image:none;box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(34,36,38,.15) inset;color:rgba(0,0,0,.8)}.ui.button:hover .icon{opacity:.85}.ui.button:focus{background-color:#CACBCD;color:rgba(0,0,0,.8);background-image:''!important;box-shadow:''!important}.ui.button:focus .icon{opacity:.85}.ui.active.button:active,.ui.button:active{background-color:#BABBBC;background-image:'';color:rgba(0,0,0,.9);box-shadow:0 0 0 1px transparent inset,none}.ui.active.button{background-color:#C0C1C2;background-image:none;box-shadow:0 0 0 1px transparent inset;color:rgba(0,0,0,.95)}.ui.active.button:hover{background-color:#C0C1C2;background-image:none;color:rgba(0,0,0,.95)}.ui.active.button:active{background-color:#C0C1C2;background-image:none}.ui.loading.loading.loading.loading.loading.loading.button{position:relative;cursor:default;text-shadow:none!important;color:transparent!important;opacity:1;pointer-events:auto;-webkit-transition:all 0s linear,opacity .1s ease;transition:all 0s linear,opacity .1s ease}.ui.loading.button:before{position:absolute;content:'';top:50%;left:50%;margin:-.64285714em 0 0 -.64285714em;width:1.28571429em;height:1.28571429em;border-radius:500rem;border:.2em solid rgba(0,0,0,.15)}.ui.loading.button:after{position:absolute;content:'';top:50%;left:50%;margin:-.64285714em 0 0 -.64285714em;width:1.28571429em;height:1.28571429em;-webkit-animation:button-spin .6s linear;animation:button-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#FFF transparent transparent;border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent}.ui.labeled.icon.loading.button .icon{background-color:transparent;box-shadow:none}@-webkit-keyframes button-spin{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes button-spin{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ui.basic.loading.button:not(.inverted):before{border-color:rgba(0,0,0,.1)}.ui.basic.loading.button:not(.inverted):after{border-top-color:#767676}.ui.button:disabled,.ui.buttons .disabled.button,.ui.disabled.active.button,.ui.disabled.button,.ui.disabled.button:hover{cursor:default;opacity:.45!important;background-image:none!important;box-shadow:none!important;pointer-events:none!important}.ui.basic.buttons .ui.disabled.button{border-color:rgba(34,36,38,.5)}.ui.animated.button{position:relative;overflow:hidden;padding-right:0!important;vertical-align:middle;z-index:1}.ui.animated.button .content{will-change:transform,opacity}.ui.animated.button .visible.content{position:relative;margin-right:1.5em;left:auto;right:0}.ui.animated.button .hidden.content{position:absolute;width:100%;top:50%;left:auto;right:-100%;margin-top:-.5em}.ui.animated.button .hidden.content,.ui.animated.button .visible.content{-webkit-transition:right .3s ease 0s;transition:right .3s ease 0s}.ui.animated.button:focus .visible.content,.ui.animated.button:hover .visible.content{left:auto;right:200%}.ui.animated.button:focus .hidden.content,.ui.animated.button:hover .hidden.content{left:auto;right:0}.ui.vertical.animated.button .hidden.content,.ui.vertical.animated.button .visible.content{-webkit-transition:top .3s ease,-webkit-transform .3s ease;transition:top .3s ease,-webkit-transform .3s ease;transition:top .3s ease,transform .3s ease;transition:top .3s ease,transform .3s ease,-webkit-transform .3s ease}.ui.vertical.animated.button .visible.content{-webkit-transform:translateY(0);transform:translateY(0);right:auto}.ui.vertical.animated.button .hidden.content{top:-50%;left:0;right:auto}.ui.vertical.animated.button:focus .visible.content,.ui.vertical.animated.button:hover .visible.content{-webkit-transform:translateY(200%);transform:translateY(200%);right:auto}.ui.vertical.animated.button:focus .hidden.content,.ui.vertical.animated.button:hover .hidden.content{top:50%;right:auto}.ui.fade.animated.button .hidden.content,.ui.fade.animated.button .visible.content{-webkit-transition:opacity .3s ease,-webkit-transform .3s ease;transition:opacity .3s ease,-webkit-transform .3s ease;transition:opacity .3s ease,transform .3s ease;transition:opacity .3s ease,transform .3s ease,-webkit-transform .3s ease}.ui.fade.animated.button .visible.content{left:auto;right:auto;opacity:1;-webkit-transform:scale(1);transform:scale(1)}.ui.fade.animated.button .hidden.content{opacity:0;left:0;right:auto;-webkit-transform:scale(1.5);transform:scale(1.5)}.ui.fade.animated.button:focus .visible.content,.ui.fade.animated.button:hover .visible.content{left:auto;right:auto;opacity:0;-webkit-transform:scale(.75);transform:scale(.75)}.ui.fade.animated.button:focus .hidden.content,.ui.fade.animated.button:hover .hidden.content{left:0;right:auto;opacity:1;-webkit-transform:scale(1);transform:scale(1)}.ui.inverted.button{box-shadow:0 0 0 2px #FFF inset!important;background:0 0;color:#FFF;text-shadow:none!important}.ui.inverted.buttons .button{margin:0 0 0 -2px}.ui.inverted.buttons .button:first-child{margin-left:0}.ui.inverted.vertical.buttons .button{margin:0 0 -2px}.ui.inverted.vertical.buttons .button:first-child{margin-top:0}.ui.inverted.button.active,.ui.inverted.button:focus,.ui.inverted.button:hover{background:#FFF;box-shadow:0 0 0 2px #FFF inset!important;color:rgba(0,0,0,.8)}.ui.inverted.button.active:focus{background:#DCDDDE;box-shadow:0 0 0 2px #DCDDDE inset!important;color:rgba(0,0,0,.8)}.ui.labeled.button:not(.icon){display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;background:0 0!important;padding:0!important;border:none!important;box-shadow:none!important}.ui.labeled.button>.button{margin:0}.ui.labeled.button>.label{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;margin:0 0 0 -1px!important;padding:'';font-size:1em;border-color:rgba(34,36,38,.15)}.ui.labeled.button>.tag.label:before{width:1.85em;height:1.85em}.ui.labeled.button:not([class*="left labeled"])>.button{border-top-right-radius:0;border-bottom-right-radius:0}.ui.labeled.button:not([class*="left labeled"])>.label,.ui[class*="left labeled"].button>.button{border-top-left-radius:0;border-bottom-left-radius:0}.ui[class*="left labeled"].button>.label{border-top-right-radius:0;border-bottom-right-radius:0}.ui.facebook.button{background-color:#3B5998;color:#FFF;text-shadow:none;background-image:none;box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.facebook.button:hover{background-color:#304d8a;color:#FFF;text-shadow:none}.ui.facebook.button:active{background-color:#2d4373;color:#FFF;text-shadow:none}.ui.twitter.button{background-color:#55ACEE;color:#FFF;text-shadow:none;background-image:none;box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.twitter.button:hover{background-color:#35a2f4;color:#FFF;text-shadow:none}.ui.twitter.button:active{background-color:#2795e9;color:#FFF;text-shadow:none}.ui.google.plus.button{background-color:#DD4B39;color:#FFF;text-shadow:none;background-image:none;box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.google.plus.button:hover{background-color:#e0321c;color:#FFF;text-shadow:none}.ui.google.plus.button:active{background-color:#c23321;color:#FFF;text-shadow:none}.ui.linkedin.button{background-color:#1F88BE;color:#FFF;text-shadow:none}.ui.linkedin.button:hover{background-color:#147baf;color:#FFF;text-shadow:none}.ui.linkedin.button:active{background-color:#186992;color:#FFF;text-shadow:none}.ui.youtube.button{background-color:#CC181E;color:#FFF;text-shadow:none;background-image:none;box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.youtube.button:hover{background-color:#bd0d13;color:#FFF;text-shadow:none}.ui.youtube.button:active{background-color:#9e1317;color:#FFF;text-shadow:none}.ui.instagram.button{background-color:#49769C;color:#FFF;text-shadow:none;background-image:none;box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.instagram.button:hover{background-color:#3d698e;color:#FFF;text-shadow:none}.ui.instagram.button:active{background-color:#395c79;color:#FFF;text-shadow:none}.ui.pinterest.button{background-color:#BD081C;color:#FFF;text-shadow:none;background-image:none;box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.pinterest.button:hover{background-color:#ac0013;color:#FFF;text-shadow:none}.ui.pinterest.button:active{background-color:#8c0615;color:#FFF;text-shadow:none}.ui.vk.button{background-color:#4D7198;color:#FFF;background-image:none;box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.vk.button:hover{background-color:#41648a;color:#FFF}.ui.vk.button:active{background-color:#3c5876;color:#FFF}.ui.button>.icon:not(.button){height:.85714286em;opacity:.8;margin:0 .42857143em 0 -.21428571em;-webkit-transition:opacity .1s ease;transition:opacity .1s ease;vertical-align:'';color:''}.ui.button:not(.icon)>.icon:not(.button):not(.dropdown){margin:0 .42857143em 0 -.21428571em}.ui.button:not(.icon)>.right.icon:not(.button):not(.dropdown){margin:0 -.21428571em 0 .42857143em}.ui[class*="left floated"].button,.ui[class*="left floated"].buttons{float:left;margin-left:0;margin-right:.25em}.ui[class*="right floated"].button,.ui[class*="right floated"].buttons{float:right;margin-right:0;margin-left:.25em}.ui.compact.button,.ui.compact.buttons .button{padding:.58928571em 1.125em}.ui.compact.icon.button,.ui.compact.icon.buttons .button{padding:.58928571em}.ui.compact.labeled.icon.button,.ui.compact.labeled.icon.buttons .button{padding:.58928571em 3.69642857em}.ui.mini.button,.ui.mini.buttons .button,.ui.mini.buttons .or{font-size:.78571429rem}.ui.tiny.button,.ui.tiny.buttons .button,.ui.tiny.buttons .or{font-size:.85714286rem}.ui.small.button,.ui.small.buttons .button,.ui.small.buttons .or{font-size:.92857143rem}.ui.button,.ui.buttons .button,.ui.buttons .or{font-size:1rem}.ui.large.button,.ui.large.buttons .button,.ui.large.buttons .or{font-size:1.14285714rem}.ui.big.button,.ui.big.buttons .button,.ui.big.buttons .or{font-size:1.28571429rem}.ui.huge.button,.ui.huge.buttons .button,.ui.huge.buttons .or{font-size:1.42857143rem}.ui.massive.button,.ui.massive.buttons .button,.ui.massive.buttons .or{font-size:1.71428571rem}.ui.icon.button,.ui.icon.buttons .button{padding:.78571429em}.ui.icon.button>.icon,.ui.icon.buttons .button>.icon{opacity:.9;margin:0;vertical-align:top}.ui.basic.button,.ui.basic.buttons .button{background:0 0!important;color:rgba(0,0,0,.6)!important;font-weight:400;border-radius:.28571429rem;text-transform:none;text-shadow:none!important;box-shadow:0 0 0 1px rgba(34,36,38,.15) inset}.ui.basic.buttons{box-shadow:none;border:1px solid rgba(34,36,38,.15);border-radius:.28571429rem}.ui.basic.button:focus,.ui.basic.button:hover,.ui.basic.buttons .button:focus,.ui.basic.buttons .button:hover{background:#FFF!important;color:rgba(0,0,0,.8)!important;box-shadow:0 0 0 1px rgba(34,36,38,.35) inset,0 0 0 0 rgba(34,36,38,.15) inset}.ui.basic.button:active,.ui.basic.buttons .button:active{background:#F8F8F8!important;color:rgba(0,0,0,.9)!important;box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 1px 4px 0 rgba(34,36,38,.15) inset}.ui.basic.active.button,.ui.basic.buttons .active.button{background:rgba(0,0,0,.05)!important;box-shadow:''!important;color:rgba(0,0,0,.95)}.ui.basic.active.button:hover,.ui.basic.buttons .active.button:hover{background-color:rgba(0,0,0,.05)}.ui.basic.buttons .button:hover{box-shadow:0 0 0 1px rgba(34,36,38,.35) inset,0 0 0 0 rgba(34,36,38,.15) inset inset}.ui.basic.buttons .button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 1px 4px 0 rgba(34,36,38,.15) inset inset}.ui.basic.buttons .active.button{box-shadow:rgba(34,36,38,.35) inset}.ui.basic.inverted.button,.ui.basic.inverted.buttons .button{background-color:transparent!important;color:#F9FAFB!important;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important}.ui.basic.inverted.button:focus,.ui.basic.inverted.button:hover,.ui.basic.inverted.buttons .button:focus,.ui.basic.inverted.buttons .button:hover{color:#FFF!important;box-shadow:0 0 0 2px #fff inset!important}.ui.basic.inverted.button:active,.ui.basic.inverted.buttons .button:active{background-color:rgba(255,255,255,.08)!important;color:#FFF!important;box-shadow:0 0 0 2px rgba(255,255,255,.9) inset!important}.ui.basic.inverted.active.button,.ui.basic.inverted.buttons .active.button{background-color:rgba(255,255,255,.08);color:#FFF;text-shadow:none;box-shadow:0 0 0 2px rgba(255,255,255,.7) inset}.ui.basic.inverted.active.button:hover,.ui.basic.inverted.buttons .active.button:hover{background-color:rgba(255,255,255,.15);box-shadow:0 0 0 2px #fff inset!important}.ui.basic.buttons .button{border-radius:0;border-left:1px solid rgba(34,36,38,.15);box-shadow:none}.ui.basic.vertical.buttons .button{border-left:none;border-left-width:0;border-top:1px solid rgba(34,36,38,.15)}.ui.basic.vertical.buttons .button:first-child{border-top-width:0}.ui.labeled.icon.button,.ui.labeled.icon.buttons .button{position:relative;padding-left:4.07142857em!important;padding-right:1.5em!important}.ui.labeled.icon.button>.icon,.ui.labeled.icon.buttons>.button>.icon{position:absolute;height:100%;line-height:1;border-radius:0;border-top-left-radius:inherit;border-bottom-left-radius:inherit;text-align:center;margin:0;width:2.57142857em;background-color:rgba(0,0,0,.05);color:'';box-shadow:-1px 0 0 0 transparent inset;top:0;left:0}.ui[class*="right labeled"].icon.button{padding-right:4.07142857em!important;padding-left:1.5em!important}.ui[class*="right labeled"].icon.button>.icon{left:auto;right:0;border-radius:0;border-top-right-radius:inherit;border-bottom-right-radius:inherit;box-shadow:1px 0 0 0 transparent inset}.ui.labeled.icon.button>.icon:after,.ui.labeled.icon.button>.icon:before,.ui.labeled.icon.buttons>.button>.icon:after,.ui.labeled.icon.buttons>.button>.icon:before{display:block;position:absolute;width:100%;top:50%;text-align:center;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.ui.labeled.icon.buttons .button>.icon{border-radius:0}.ui.labeled.icon.buttons .button:first-child>.icon{border-top-left-radius:.28571429rem;border-bottom-left-radius:.28571429rem}.ui.labeled.icon.buttons .button:last-child>.icon{border-top-right-radius:.28571429rem;border-bottom-right-radius:.28571429rem}.ui.vertical.labeled.icon.buttons .button:first-child>.icon{border-radius:.28571429rem 0 0}.ui.vertical.labeled.icon.buttons .button:last-child>.icon{border-radius:0 0 0 .28571429rem}.ui.fluid[class*="left labeled"].icon.button,.ui.fluid[class*="right labeled"].icon.button{padding-left:1.5em!important;padding-right:1.5em!important}.ui.button.toggle.active,.ui.buttons .button.toggle.active,.ui.toggle.buttons .active.button{background-color:#21BA45!important;box-shadow:none!important;text-shadow:none;color:#FFF!important}.ui.button.toggle.active:hover{background-color:#16ab39!important;text-shadow:none;color:#FFF!important}.ui.circular.button{border-radius:10em}.ui.circular.button>.icon{width:1em;vertical-align:baseline}.ui.buttons .or{position:relative;width:.3em;height:2.57142857em;z-index:3}.ui.buttons .or:before{position:absolute;text-align:center;border-radius:500rem;content:'or';top:50%;left:50%;background-color:#FFF;text-shadow:none;margin-top:-.89285714em;margin-left:-.89285714em;width:1.78571429em;height:1.78571429em;line-height:1.78571429em;color:rgba(0,0,0,.4);font-style:normal;font-weight:700;box-shadow:0 0 0 1px transparent inset}.ui.buttons .or[data-text]:before{content:attr(data-text)}.ui.fluid.buttons .or{width:0!important}.ui.fluid.buttons .or:after{display:none}.ui.attached.button{position:relative;display:block;margin:0;border-radius:0;box-shadow:0 0 0 1px rgba(34,36,38,.15)!important}.ui.attached.top.button{border-radius:.28571429rem .28571429rem 0 0}.ui.attached.bottom.button{border-radius:0 0 .28571429rem .28571429rem}.ui.left.attached.button{display:inline-block;border-left:none;text-align:right;padding-right:.75em;border-radius:.28571429rem 0 0 .28571429rem}.ui.right.attached.button{display:inline-block;text-align:left;padding-left:.75em;border-radius:0 .28571429rem .28571429rem 0}.ui.attached.buttons{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;border-radius:0;width:auto!important;z-index:2;margin-left:-1px;margin-right:-1px}.ui.attached.buttons .button{margin:0}.ui.attached.buttons .button:first-child,.ui.attached.buttons .button:last-child{border-radius:0}.ui[class*="top attached"].buttons{margin-bottom:-1px;border-radius:.28571429rem .28571429rem 0 0}.ui[class*="top attached"].buttons .button:first-child{border-radius:.28571429rem 0 0}.ui[class*="top attached"].buttons .button:last-child{border-radius:0 .28571429rem 0 0}.ui[class*="bottom attached"].buttons{margin-top:-1px;border-radius:0 0 .28571429rem .28571429rem}.ui[class*="bottom attached"].buttons .button:first-child{border-radius:0 0 0 .28571429rem}.ui[class*="bottom attached"].buttons .button:last-child{border-radius:0 0 .28571429rem}.ui[class*="left attached"].buttons{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;margin-right:0;margin-left:-1px;border-radius:0 .28571429rem .28571429rem 0}.ui[class*="left attached"].buttons .button:first-child{margin-left:-1px;border-radius:0 .28571429rem 0 0}.ui[class*="left attached"].buttons .button:last-child{margin-left:-1px;border-radius:0 0 .28571429rem}.ui[class*="right attached"].buttons{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;margin-left:0;margin-right:-1px;border-radius:.28571429rem 0 0 .28571429rem}.ui[class*="right attached"].buttons .button:first-child{margin-left:-1px;border-radius:.28571429rem 0 0}.ui[class*="right attached"].buttons .button:last-child{margin-left:-1px;border-radius:0 0 0 .28571429rem}.ui.fluid.button,.ui.fluid.buttons{width:100%}.ui.fluid.button{display:block}.ui.two.buttons{width:100%}.ui.two.buttons>.button{width:50%}.ui.three.buttons{width:100%}.ui.three.buttons>.button{width:33.333%}.ui.four.buttons{width:100%}.ui.four.buttons>.button{width:25%}.ui.five.buttons{width:100%}.ui.five.buttons>.button{width:20%}.ui.six.buttons{width:100%}.ui.six.buttons>.button{width:16.666%}.ui.seven.buttons{width:100%}.ui.seven.buttons>.button{width:14.285%}.ui.eight.buttons{width:100%}.ui.eight.buttons>.button{width:12.5%}.ui.nine.buttons{width:100%}.ui.nine.buttons>.button{width:11.11%}.ui.ten.buttons{width:100%}.ui.ten.buttons>.button{width:10%}.ui.eleven.buttons{width:100%}.ui.eleven.buttons>.button{width:9.09%}.ui.twelve.buttons{width:100%}.ui.twelve.buttons>.button{width:8.3333%}.ui.fluid.vertical.buttons,.ui.fluid.vertical.buttons>.button{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:auto}.ui.two.vertical.buttons>.button{height:50%}.ui.three.vertical.buttons>.button{height:33.333%}.ui.four.vertical.buttons>.button{height:25%}.ui.five.vertical.buttons>.button{height:20%}.ui.six.vertical.buttons>.button{height:16.666%}.ui.seven.vertical.buttons>.button{height:14.285%}.ui.eight.vertical.buttons>.button{height:12.5%}.ui.nine.vertical.buttons>.button{height:11.11%}.ui.ten.vertical.buttons>.button{height:10%}.ui.eleven.vertical.buttons>.button{height:9.09%}.ui.twelve.vertical.buttons>.button{height:8.3333%}.ui.black.button,.ui.black.buttons .button{background-color:#1B1C1D;color:#FFF;text-shadow:none;background-image:none}.ui.black.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.black.button:hover,.ui.black.buttons .button:hover{background-color:#27292a;color:#FFF;text-shadow:none}.ui.black.button:focus,.ui.black.buttons .button:focus{background-color:#2f3032;color:#FFF;text-shadow:none}.ui.black.button:active,.ui.black.buttons .button:active{background-color:#343637;color:#FFF;text-shadow:none}.ui.black.active.button,.ui.black.button .active.button:active,.ui.black.buttons .active.button,.ui.black.buttons .active.button:active{background-color:#0f0f10;color:#FFF;text-shadow:none}.ui.basic.black.button,.ui.basic.black.buttons .button{box-shadow:0 0 0 1px #1B1C1D inset!important;color:#1B1C1D!important}.ui.basic.black.button:hover,.ui.basic.black.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #27292a inset!important;color:#27292a!important}.ui.basic.black.button:focus,.ui.basic.black.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #2f3032 inset!important;color:#27292a!important}.ui.basic.black.active.button,.ui.basic.black.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #0f0f10 inset!important;color:#343637!important}.ui.basic.black.button:active,.ui.basic.black.buttons .button:active{box-shadow:0 0 0 1px #343637 inset!important;color:#343637!important}.ui.buttons:not(.vertical)>.basic.black.button:not(:first-child){margin-left:-1px}.ui.inverted.black.button,.ui.inverted.black.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #D4D4D5 inset!important;color:#FFF}.ui.inverted.black.button.active,.ui.inverted.black.button:active,.ui.inverted.black.button:focus,.ui.inverted.black.button:hover,.ui.inverted.black.buttons .button.active,.ui.inverted.black.buttons .button:active,.ui.inverted.black.buttons .button:focus,.ui.inverted.black.buttons .button:hover{box-shadow:none!important;color:#FFF}.ui.inverted.black.active.button,.ui.inverted.black.button:active,.ui.inverted.black.button:focus,.ui.inverted.black.button:hover,.ui.inverted.black.buttons .active.button,.ui.inverted.black.buttons .button:active,.ui.inverted.black.buttons .button:focus,.ui.inverted.black.buttons .button:hover{background-color:#000}.ui.inverted.black.basic.button,.ui.inverted.black.basic.buttons .button,.ui.inverted.black.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.black.basic.button:hover,.ui.inverted.black.basic.buttons .button:hover,.ui.inverted.black.buttons .basic.button:hover{box-shadow:0 0 0 2px #000 inset!important;color:#FFF!important}.ui.inverted.black.basic.button:focus,.ui.inverted.black.basic.buttons .button:focus{box-shadow:0 0 0 2px #000 inset!important;color:#545454!important}.ui.inverted.black.basic.active.button,.ui.inverted.black.basic.button:active,.ui.inverted.black.basic.buttons .active.button,.ui.inverted.black.basic.buttons .button:active,.ui.inverted.black.buttons .basic.active.button,.ui.inverted.black.buttons .basic.button:active{box-shadow:0 0 0 2px #000 inset!important;color:#FFF!important}.ui.grey.button,.ui.grey.buttons .button{background-color:#767676;color:#FFF;text-shadow:none;background-image:none}.ui.grey.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.grey.button:hover,.ui.grey.buttons .button:hover{background-color:#838383;color:#FFF;text-shadow:none}.ui.grey.button:focus,.ui.grey.buttons .button:focus{background-color:#8a8a8a;color:#FFF;text-shadow:none}.ui.grey.button:active,.ui.grey.buttons .button:active{background-color:#909090;color:#FFF;text-shadow:none}.ui.grey.active.button,.ui.grey.button .active.button:active,.ui.grey.buttons .active.button,.ui.grey.buttons .active.button:active{background-color:#696969;color:#FFF;text-shadow:none}.ui.basic.grey.button,.ui.basic.grey.buttons .button{box-shadow:0 0 0 1px #767676 inset!important;color:#767676!important}.ui.basic.grey.button:hover,.ui.basic.grey.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #838383 inset!important;color:#838383!important}.ui.basic.grey.button:focus,.ui.basic.grey.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #8a8a8a inset!important;color:#838383!important}.ui.basic.grey.active.button,.ui.basic.grey.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #696969 inset!important;color:#909090!important}.ui.basic.grey.button:active,.ui.basic.grey.buttons .button:active{box-shadow:0 0 0 1px #909090 inset!important;color:#909090!important}.ui.buttons:not(.vertical)>.basic.grey.button:not(:first-child){margin-left:-1px}.ui.inverted.grey.button,.ui.inverted.grey.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #D4D4D5 inset!important;color:#FFF}.ui.inverted.grey.button.active,.ui.inverted.grey.button:active,.ui.inverted.grey.button:focus,.ui.inverted.grey.button:hover,.ui.inverted.grey.buttons .button.active,.ui.inverted.grey.buttons .button:active,.ui.inverted.grey.buttons .button:focus,.ui.inverted.grey.buttons .button:hover{box-shadow:none!important;color:rgba(0,0,0,.6)}.ui.inverted.grey.button:hover,.ui.inverted.grey.buttons .button:hover{background-color:#cfd0d2}.ui.inverted.grey.button:focus,.ui.inverted.grey.buttons .button:focus{background-color:#c7c9cb}.ui.inverted.grey.active.button,.ui.inverted.grey.buttons .active.button{background-color:#cfd0d2}.ui.inverted.grey.button:active,.ui.inverted.grey.buttons .button:active{background-color:#c2c4c5}.ui.inverted.grey.basic.button,.ui.inverted.grey.basic.buttons .button,.ui.inverted.grey.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.grey.basic.button:hover,.ui.inverted.grey.basic.buttons .button:hover,.ui.inverted.grey.buttons .basic.button:hover{box-shadow:0 0 0 2px #cfd0d2 inset!important;color:#FFF!important}.ui.inverted.grey.basic.button:focus,.ui.inverted.grey.basic.buttons .button:focus{box-shadow:0 0 0 2px #c7c9cb inset!important;color:#DCDDDE!important}.ui.inverted.grey.basic.active.button,.ui.inverted.grey.basic.buttons .active.button,.ui.inverted.grey.buttons .basic.active.button{box-shadow:0 0 0 2px #cfd0d2 inset!important;color:#FFF!important}.ui.inverted.grey.basic.button:active,.ui.inverted.grey.basic.buttons .button:active,.ui.inverted.grey.buttons .basic.button:active{box-shadow:0 0 0 2px #c2c4c5 inset!important;color:#FFF!important}.ui.brown.button,.ui.brown.buttons .button{background-color:#A5673F;color:#FFF;text-shadow:none;background-image:none}.ui.brown.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.brown.button:hover,.ui.brown.buttons .button:hover{background-color:#975b33;color:#FFF;text-shadow:none}.ui.brown.button:focus,.ui.brown.buttons .button:focus{background-color:#90532b;color:#FFF;text-shadow:none}.ui.brown.button:active,.ui.brown.buttons .button:active{background-color:#805031;color:#FFF;text-shadow:none}.ui.brown.active.button,.ui.brown.button .active.button:active,.ui.brown.buttons .active.button,.ui.brown.buttons .active.button:active{background-color:#995a31;color:#FFF;text-shadow:none}.ui.basic.brown.button,.ui.basic.brown.buttons .button{box-shadow:0 0 0 1px #A5673F inset!important;color:#A5673F!important}.ui.basic.brown.button:hover,.ui.basic.brown.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #975b33 inset!important;color:#975b33!important}.ui.basic.brown.button:focus,.ui.basic.brown.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #90532b inset!important;color:#975b33!important}.ui.basic.brown.active.button,.ui.basic.brown.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #995a31 inset!important;color:#805031!important}.ui.basic.brown.button:active,.ui.basic.brown.buttons .button:active{box-shadow:0 0 0 1px #805031 inset!important;color:#805031!important}.ui.buttons:not(.vertical)>.basic.brown.button:not(:first-child){margin-left:-1px}.ui.inverted.brown.button,.ui.inverted.brown.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #D67C1C inset!important;color:#D67C1C}.ui.inverted.brown.button.active,.ui.inverted.brown.button:active,.ui.inverted.brown.button:focus,.ui.inverted.brown.button:hover,.ui.inverted.brown.buttons .button.active,.ui.inverted.brown.buttons .button:active,.ui.inverted.brown.buttons .button:focus,.ui.inverted.brown.buttons .button:hover{box-shadow:none!important;color:#FFF}.ui.inverted.brown.button:hover,.ui.inverted.brown.buttons .button:hover{background-color:#c86f11}.ui.inverted.brown.button:focus,.ui.inverted.brown.buttons .button:focus{background-color:#c16808}.ui.inverted.brown.active.button,.ui.inverted.brown.buttons .active.button{background-color:#cc6f0d}.ui.inverted.brown.button:active,.ui.inverted.brown.buttons .button:active{background-color:#a96216}.ui.inverted.brown.basic.button,.ui.inverted.brown.basic.buttons .button,.ui.inverted.brown.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.brown.basic.button:hover,.ui.inverted.brown.basic.buttons .button:hover,.ui.inverted.brown.buttons .basic.button:hover{box-shadow:0 0 0 2px #c86f11 inset!important;color:#D67C1C!important}.ui.inverted.brown.basic.button:focus,.ui.inverted.brown.basic.buttons .button:focus{box-shadow:0 0 0 2px #c16808 inset!important;color:#D67C1C!important}.ui.inverted.brown.basic.active.button,.ui.inverted.brown.basic.buttons .active.button,.ui.inverted.brown.buttons .basic.active.button{box-shadow:0 0 0 2px #cc6f0d inset!important;color:#D67C1C!important}.ui.inverted.brown.basic.button:active,.ui.inverted.brown.basic.buttons .button:active,.ui.inverted.brown.buttons .basic.button:active{box-shadow:0 0 0 2px #a96216 inset!important;color:#D67C1C!important}.ui.blue.button,.ui.blue.buttons .button{background-color:#2185D0;color:#FFF;text-shadow:none;background-image:none}.ui.blue.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.blue.button:hover,.ui.blue.buttons .button:hover{background-color:#1678c2;color:#FFF;text-shadow:none}.ui.blue.button:focus,.ui.blue.buttons .button:focus{background-color:#0d71bb;color:#FFF;text-shadow:none}.ui.blue.button:active,.ui.blue.buttons .button:active{background-color:#1a69a4;color:#FFF;text-shadow:none}.ui.blue.active.button,.ui.blue.button .active.button:active,.ui.blue.buttons .active.button,.ui.blue.buttons .active.button:active{background-color:#1279c6;color:#FFF;text-shadow:none}.ui.basic.blue.button,.ui.basic.blue.buttons .button{box-shadow:0 0 0 1px #2185D0 inset!important;color:#2185D0!important}.ui.basic.blue.button:hover,.ui.basic.blue.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #1678c2 inset!important;color:#1678c2!important}.ui.basic.blue.button:focus,.ui.basic.blue.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #0d71bb inset!important;color:#1678c2!important}.ui.basic.blue.active.button,.ui.basic.blue.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #1279c6 inset!important;color:#1a69a4!important}.ui.basic.blue.button:active,.ui.basic.blue.buttons .button:active{box-shadow:0 0 0 1px #1a69a4 inset!important;color:#1a69a4!important}.ui.buttons:not(.vertical)>.basic.blue.button:not(:first-child){margin-left:-1px}.ui.inverted.blue.button,.ui.inverted.blue.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #54C8FF inset!important;color:#54C8FF}.ui.inverted.blue.button.active,.ui.inverted.blue.button:active,.ui.inverted.blue.button:focus,.ui.inverted.blue.button:hover,.ui.inverted.blue.buttons .button.active,.ui.inverted.blue.buttons .button:active,.ui.inverted.blue.buttons .button:focus,.ui.inverted.blue.buttons .button:hover{box-shadow:none!important;color:#FFF}.ui.inverted.blue.button:hover,.ui.inverted.blue.buttons .button:hover{background-color:#3ac0ff}.ui.inverted.blue.button:focus,.ui.inverted.blue.buttons .button:focus{background-color:#2bbbff}.ui.inverted.blue.active.button,.ui.inverted.blue.buttons .active.button{background-color:#3ac0ff}.ui.inverted.blue.button:active,.ui.inverted.blue.buttons .button:active{background-color:#21b8ff}.ui.inverted.blue.basic.button,.ui.inverted.blue.basic.buttons .button,.ui.inverted.blue.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.blue.basic.button:hover,.ui.inverted.blue.basic.buttons .button:hover,.ui.inverted.blue.buttons .basic.button:hover{box-shadow:0 0 0 2px #3ac0ff inset!important;color:#54C8FF!important}.ui.inverted.blue.basic.button:focus,.ui.inverted.blue.basic.buttons .button:focus{box-shadow:0 0 0 2px #2bbbff inset!important;color:#54C8FF!important}.ui.inverted.blue.basic.active.button,.ui.inverted.blue.basic.buttons .active.button,.ui.inverted.blue.buttons .basic.active.button{box-shadow:0 0 0 2px #3ac0ff inset!important;color:#54C8FF!important}.ui.inverted.blue.basic.button:active,.ui.inverted.blue.basic.buttons .button:active,.ui.inverted.blue.buttons .basic.button:active{box-shadow:0 0 0 2px #21b8ff inset!important;color:#54C8FF!important}.ui.green.button,.ui.green.buttons .button{background-color:#21BA45;color:#FFF;text-shadow:none;background-image:none}.ui.green.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.green.button:hover,.ui.green.buttons .button:hover{background-color:#16ab39;color:#FFF;text-shadow:none}.ui.green.button:focus,.ui.green.buttons .button:focus{background-color:#0ea432;color:#FFF;text-shadow:none}.ui.green.button:active,.ui.green.buttons .button:active{background-color:#198f35;color:#FFF;text-shadow:none}.ui.green.active.button,.ui.green.button .active.button:active,.ui.green.buttons .active.button,.ui.green.buttons .active.button:active{background-color:#13ae38;color:#FFF;text-shadow:none}.ui.basic.green.button,.ui.basic.green.buttons .button{box-shadow:0 0 0 1px #21BA45 inset!important;color:#21BA45!important}.ui.basic.green.button:hover,.ui.basic.green.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #16ab39 inset!important;color:#16ab39!important}.ui.basic.green.button:focus,.ui.basic.green.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #0ea432 inset!important;color:#16ab39!important}.ui.basic.green.active.button,.ui.basic.green.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #13ae38 inset!important;color:#198f35!important}.ui.basic.green.button:active,.ui.basic.green.buttons .button:active{box-shadow:0 0 0 1px #198f35 inset!important;color:#198f35!important}.ui.buttons:not(.vertical)>.basic.green.button:not(:first-child){margin-left:-1px}.ui.inverted.green.button,.ui.inverted.green.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #2ECC40 inset!important;color:#2ECC40}.ui.inverted.green.button.active,.ui.inverted.green.button:active,.ui.inverted.green.button:focus,.ui.inverted.green.button:hover,.ui.inverted.green.buttons .button.active,.ui.inverted.green.buttons .button:active,.ui.inverted.green.buttons .button:focus,.ui.inverted.green.buttons .button:hover{box-shadow:none!important;color:#FFF}.ui.inverted.green.button:hover,.ui.inverted.green.buttons .button:hover{background-color:#22be34}.ui.inverted.green.button:focus,.ui.inverted.green.buttons .button:focus{background-color:#19b82b}.ui.inverted.green.active.button,.ui.inverted.green.buttons .active.button{background-color:#1fc231}.ui.inverted.green.button:active,.ui.inverted.green.buttons .button:active{background-color:#25a233}.ui.inverted.green.basic.button,.ui.inverted.green.basic.buttons .button,.ui.inverted.green.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.green.basic.button:hover,.ui.inverted.green.basic.buttons .button:hover,.ui.inverted.green.buttons .basic.button:hover{box-shadow:0 0 0 2px #22be34 inset!important;color:#2ECC40!important}.ui.inverted.green.basic.button:focus,.ui.inverted.green.basic.buttons .button:focus{box-shadow:0 0 0 2px #19b82b inset!important;color:#2ECC40!important}.ui.inverted.green.basic.active.button,.ui.inverted.green.basic.buttons .active.button,.ui.inverted.green.buttons .basic.active.button{box-shadow:0 0 0 2px #1fc231 inset!important;color:#2ECC40!important}.ui.inverted.green.basic.button:active,.ui.inverted.green.basic.buttons .button:active,.ui.inverted.green.buttons .basic.button:active{box-shadow:0 0 0 2px #25a233 inset!important;color:#2ECC40!important}.ui.orange.button,.ui.orange.buttons .button{background-color:#F2711C;color:#FFF;text-shadow:none;background-image:none}.ui.orange.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.orange.button:hover,.ui.orange.buttons .button:hover{background-color:#f26202;color:#FFF;text-shadow:none}.ui.orange.button:focus,.ui.orange.buttons .button:focus{background-color:#e55b00;color:#FFF;text-shadow:none}.ui.orange.button:active,.ui.orange.buttons .button:active{background-color:#cf590c;color:#FFF;text-shadow:none}.ui.orange.active.button,.ui.orange.button .active.button:active,.ui.orange.buttons .active.button,.ui.orange.buttons .active.button:active{background-color:#f56100;color:#FFF;text-shadow:none}.ui.basic.orange.button,.ui.basic.orange.buttons .button{box-shadow:0 0 0 1px #F2711C inset!important;color:#F2711C!important}.ui.basic.orange.button:hover,.ui.basic.orange.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #f26202 inset!important;color:#f26202!important}.ui.basic.orange.button:focus,.ui.basic.orange.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #e55b00 inset!important;color:#f26202!important}.ui.basic.orange.active.button,.ui.basic.orange.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #f56100 inset!important;color:#cf590c!important}.ui.basic.orange.button:active,.ui.basic.orange.buttons .button:active{box-shadow:0 0 0 1px #cf590c inset!important;color:#cf590c!important}.ui.buttons:not(.vertical)>.basic.orange.button:not(:first-child){margin-left:-1px}.ui.inverted.orange.button,.ui.inverted.orange.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #FF851B inset!important;color:#FF851B}.ui.inverted.orange.button.active,.ui.inverted.orange.button:active,.ui.inverted.orange.button:focus,.ui.inverted.orange.button:hover,.ui.inverted.orange.buttons .button.active,.ui.inverted.orange.buttons .button:active,.ui.inverted.orange.buttons .button:focus,.ui.inverted.orange.buttons .button:hover{box-shadow:none!important;color:#FFF}.ui.inverted.orange.button:hover,.ui.inverted.orange.buttons .button:hover{background-color:#ff7701}.ui.inverted.orange.button:focus,.ui.inverted.orange.buttons .button:focus{background-color:#f17000}.ui.inverted.orange.active.button,.ui.inverted.orange.buttons .active.button{background-color:#ff7701}.ui.inverted.orange.button:active,.ui.inverted.orange.buttons .button:active{background-color:#e76b00}.ui.inverted.orange.basic.button,.ui.inverted.orange.basic.buttons .button,.ui.inverted.orange.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.orange.basic.button:hover,.ui.inverted.orange.basic.buttons .button:hover,.ui.inverted.orange.buttons .basic.button:hover{box-shadow:0 0 0 2px #ff7701 inset!important;color:#FF851B!important}.ui.inverted.orange.basic.button:focus,.ui.inverted.orange.basic.buttons .button:focus{box-shadow:0 0 0 2px #f17000 inset!important;color:#FF851B!important}.ui.inverted.orange.basic.active.button,.ui.inverted.orange.basic.buttons .active.button,.ui.inverted.orange.buttons .basic.active.button{box-shadow:0 0 0 2px #ff7701 inset!important;color:#FF851B!important}.ui.inverted.orange.basic.button:active,.ui.inverted.orange.basic.buttons .button:active,.ui.inverted.orange.buttons .basic.button:active{box-shadow:0 0 0 2px #e76b00 inset!important;color:#FF851B!important}.ui.pink.button,.ui.pink.buttons .button{background-color:#E03997;color:#FFF;text-shadow:none;background-image:none}.ui.pink.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.pink.button:hover,.ui.pink.buttons .button:hover{background-color:#e61a8d;color:#FFF;text-shadow:none}.ui.pink.button:focus,.ui.pink.buttons .button:focus{background-color:#e10f85;color:#FFF;text-shadow:none}.ui.pink.button:active,.ui.pink.buttons .button:active{background-color:#c71f7e;color:#FFF;text-shadow:none}.ui.pink.active.button,.ui.pink.button .active.button:active,.ui.pink.buttons .active.button,.ui.pink.buttons .active.button:active{background-color:#ea158d;color:#FFF;text-shadow:none}.ui.basic.pink.button,.ui.basic.pink.buttons .button{box-shadow:0 0 0 1px #E03997 inset!important;color:#E03997!important}.ui.basic.pink.button:hover,.ui.basic.pink.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #e61a8d inset!important;color:#e61a8d!important}.ui.basic.pink.button:focus,.ui.basic.pink.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #e10f85 inset!important;color:#e61a8d!important}.ui.basic.pink.active.button,.ui.basic.pink.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #ea158d inset!important;color:#c71f7e!important}.ui.basic.pink.button:active,.ui.basic.pink.buttons .button:active{box-shadow:0 0 0 1px #c71f7e inset!important;color:#c71f7e!important}.ui.buttons:not(.vertical)>.basic.pink.button:not(:first-child){margin-left:-1px}.ui.inverted.pink.button,.ui.inverted.pink.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #FF8EDF inset!important;color:#FF8EDF}.ui.inverted.pink.button.active,.ui.inverted.pink.button:active,.ui.inverted.pink.button:focus,.ui.inverted.pink.button:hover,.ui.inverted.pink.buttons .button.active,.ui.inverted.pink.buttons .button:active,.ui.inverted.pink.buttons .button:focus,.ui.inverted.pink.buttons .button:hover{box-shadow:none!important;color:#FFF}.ui.inverted.pink.button:hover,.ui.inverted.pink.buttons .button:hover{background-color:#ff74d8}.ui.inverted.pink.button:focus,.ui.inverted.pink.buttons .button:focus{background-color:#ff65d3}.ui.inverted.pink.active.button,.ui.inverted.pink.buttons .active.button{background-color:#ff74d8}.ui.inverted.pink.button:active,.ui.inverted.pink.buttons .button:active{background-color:#ff5bd1}.ui.inverted.pink.basic.button,.ui.inverted.pink.basic.buttons .button,.ui.inverted.pink.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.pink.basic.button:hover,.ui.inverted.pink.basic.buttons .button:hover,.ui.inverted.pink.buttons .basic.button:hover{box-shadow:0 0 0 2px #ff74d8 inset!important;color:#FF8EDF!important}.ui.inverted.pink.basic.button:focus,.ui.inverted.pink.basic.buttons .button:focus{box-shadow:0 0 0 2px #ff65d3 inset!important;color:#FF8EDF!important}.ui.inverted.pink.basic.active.button,.ui.inverted.pink.basic.buttons .active.button,.ui.inverted.pink.buttons .basic.active.button{box-shadow:0 0 0 2px #ff74d8 inset!important;color:#FF8EDF!important}.ui.inverted.pink.basic.button:active,.ui.inverted.pink.basic.buttons .button:active,.ui.inverted.pink.buttons .basic.button:active{box-shadow:0 0 0 2px #ff5bd1 inset!important;color:#FF8EDF!important}.ui.violet.button,.ui.violet.buttons .button{background-color:#6435C9;color:#FFF;text-shadow:none;background-image:none}.ui.violet.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.violet.button:hover,.ui.violet.buttons .button:hover{background-color:#5829bb;color:#FFF;text-shadow:none}.ui.violet.button:focus,.ui.violet.buttons .button:focus{background-color:#4f20b5;color:#FFF;text-shadow:none}.ui.violet.button:active,.ui.violet.buttons .button:active{background-color:#502aa1;color:#FFF;text-shadow:none}.ui.violet.active.button,.ui.violet.button .active.button:active,.ui.violet.buttons .active.button,.ui.violet.buttons .active.button:active{background-color:#5626bf;color:#FFF;text-shadow:none}.ui.basic.violet.button,.ui.basic.violet.buttons .button{box-shadow:0 0 0 1px #6435C9 inset!important;color:#6435C9!important}.ui.basic.violet.button:hover,.ui.basic.violet.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #5829bb inset!important;color:#5829bb!important}.ui.basic.violet.button:focus,.ui.basic.violet.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #4f20b5 inset!important;color:#5829bb!important}.ui.basic.violet.active.button,.ui.basic.violet.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #5626bf inset!important;color:#502aa1!important}.ui.basic.violet.button:active,.ui.basic.violet.buttons .button:active{box-shadow:0 0 0 1px #502aa1 inset!important;color:#502aa1!important}.ui.buttons:not(.vertical)>.basic.violet.button:not(:first-child){margin-left:-1px}.ui.inverted.violet.button,.ui.inverted.violet.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #A291FB inset!important;color:#A291FB}.ui.inverted.violet.button.active,.ui.inverted.violet.button:active,.ui.inverted.violet.button:focus,.ui.inverted.violet.button:hover,.ui.inverted.violet.buttons .button.active,.ui.inverted.violet.buttons .button:active,.ui.inverted.violet.buttons .button:focus,.ui.inverted.violet.buttons .button:hover{box-shadow:none!important;color:#FFF}.ui.inverted.violet.button:hover,.ui.inverted.violet.buttons .button:hover{background-color:#8a73ff}.ui.inverted.violet.button:focus,.ui.inverted.violet.buttons .button:focus{background-color:#7d64ff}.ui.inverted.violet.active.button,.ui.inverted.violet.buttons .active.button{background-color:#8a73ff}.ui.inverted.violet.button:active,.ui.inverted.violet.buttons .button:active{background-color:#7860f9}.ui.inverted.violet.basic.button,.ui.inverted.violet.basic.buttons .button,.ui.inverted.violet.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.violet.basic.button:hover,.ui.inverted.violet.basic.buttons .button:hover,.ui.inverted.violet.buttons .basic.button:hover{box-shadow:0 0 0 2px #8a73ff inset!important;color:#A291FB!important}.ui.inverted.violet.basic.button:focus,.ui.inverted.violet.basic.buttons .button:focus{box-shadow:0 0 0 2px #7d64ff inset!important;color:#A291FB!important}.ui.inverted.violet.basic.active.button,.ui.inverted.violet.basic.buttons .active.button,.ui.inverted.violet.buttons .basic.active.button{box-shadow:0 0 0 2px #8a73ff inset!important;color:#A291FB!important}.ui.inverted.violet.basic.button:active,.ui.inverted.violet.basic.buttons .button:active,.ui.inverted.violet.buttons .basic.button:active{box-shadow:0 0 0 2px #7860f9 inset!important;color:#A291FB!important}.ui.purple.button,.ui.purple.buttons .button{background-color:#A333C8;color:#FFF;text-shadow:none;background-image:none}.ui.purple.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.purple.button:hover,.ui.purple.buttons .button:hover{background-color:#9627ba;color:#FFF;text-shadow:none}.ui.purple.button:focus,.ui.purple.buttons .button:focus{background-color:#8f1eb4;color:#FFF;text-shadow:none}.ui.purple.button:active,.ui.purple.buttons .button:active{background-color:#82299f;color:#FFF;text-shadow:none}.ui.purple.active.button,.ui.purple.button .active.button:active,.ui.purple.buttons .active.button,.ui.purple.buttons .active.button:active{background-color:#9724be;color:#FFF;text-shadow:none}.ui.basic.purple.button,.ui.basic.purple.buttons .button{box-shadow:0 0 0 1px #A333C8 inset!important;color:#A333C8!important}.ui.basic.purple.button:hover,.ui.basic.purple.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #9627ba inset!important;color:#9627ba!important}.ui.basic.purple.button:focus,.ui.basic.purple.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #8f1eb4 inset!important;color:#9627ba!important}.ui.basic.purple.active.button,.ui.basic.purple.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #9724be inset!important;color:#82299f!important}.ui.basic.purple.button:active,.ui.basic.purple.buttons .button:active{box-shadow:0 0 0 1px #82299f inset!important;color:#82299f!important}.ui.buttons:not(.vertical)>.basic.purple.button:not(:first-child){margin-left:-1px}.ui.inverted.purple.button,.ui.inverted.purple.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #DC73FF inset!important;color:#DC73FF}.ui.inverted.purple.button.active,.ui.inverted.purple.button:active,.ui.inverted.purple.button:focus,.ui.inverted.purple.button:hover,.ui.inverted.purple.buttons .button.active,.ui.inverted.purple.buttons .button:active,.ui.inverted.purple.buttons .button:focus,.ui.inverted.purple.buttons .button:hover{box-shadow:none!important;color:#FFF}.ui.inverted.purple.button:hover,.ui.inverted.purple.buttons .button:hover{background-color:#d65aff}.ui.inverted.purple.button:focus,.ui.inverted.purple.buttons .button:focus{background-color:#d24aff}.ui.inverted.purple.active.button,.ui.inverted.purple.buttons .active.button{background-color:#d65aff}.ui.inverted.purple.button:active,.ui.inverted.purple.buttons .button:active{background-color:#cf40ff}.ui.inverted.purple.basic.button,.ui.inverted.purple.basic.buttons .button,.ui.inverted.purple.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.purple.basic.button:hover,.ui.inverted.purple.basic.buttons .button:hover,.ui.inverted.purple.buttons .basic.button:hover{box-shadow:0 0 0 2px #d65aff inset!important;color:#DC73FF!important}.ui.inverted.purple.basic.button:focus,.ui.inverted.purple.basic.buttons .button:focus{box-shadow:0 0 0 2px #d24aff inset!important;color:#DC73FF!important}.ui.inverted.purple.basic.active.button,.ui.inverted.purple.basic.buttons .active.button,.ui.inverted.purple.buttons .basic.active.button{box-shadow:0 0 0 2px #d65aff inset!important;color:#DC73FF!important}.ui.inverted.purple.basic.button:active,.ui.inverted.purple.basic.buttons .button:active,.ui.inverted.purple.buttons .basic.button:active{box-shadow:0 0 0 2px #cf40ff inset!important;color:#DC73FF!important}.ui.red.button,.ui.red.buttons .button{background-color:#DB2828;color:#FFF;text-shadow:none;background-image:none}.ui.red.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.red.button:hover,.ui.red.buttons .button:hover{background-color:#d01919;color:#FFF;text-shadow:none}.ui.red.button:focus,.ui.red.buttons .button:focus{background-color:#ca1010;color:#FFF;text-shadow:none}.ui.red.button:active,.ui.red.buttons .button:active{background-color:#b21e1e;color:#FFF;text-shadow:none}.ui.red.active.button,.ui.red.button .active.button:active,.ui.red.buttons .active.button,.ui.red.buttons .active.button:active{background-color:#d41515;color:#FFF;text-shadow:none}.ui.basic.red.button,.ui.basic.red.buttons .button{box-shadow:0 0 0 1px #DB2828 inset!important;color:#DB2828!important}.ui.basic.red.button:hover,.ui.basic.red.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #d01919 inset!important;color:#d01919!important}.ui.basic.red.button:focus,.ui.basic.red.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #ca1010 inset!important;color:#d01919!important}.ui.basic.red.active.button,.ui.basic.red.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #d41515 inset!important;color:#b21e1e!important}.ui.basic.red.button:active,.ui.basic.red.buttons .button:active{box-shadow:0 0 0 1px #b21e1e inset!important;color:#b21e1e!important}.ui.buttons:not(.vertical)>.basic.red.button:not(:first-child){margin-left:-1px}.ui.inverted.red.button,.ui.inverted.red.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #FF695E inset!important;color:#FF695E}.ui.inverted.red.button.active,.ui.inverted.red.button:active,.ui.inverted.red.button:focus,.ui.inverted.red.button:hover,.ui.inverted.red.buttons .button.active,.ui.inverted.red.buttons .button:active,.ui.inverted.red.buttons .button:focus,.ui.inverted.red.buttons .button:hover{box-shadow:none!important;color:#FFF}.ui.inverted.red.button:hover,.ui.inverted.red.buttons .button:hover{background-color:#ff5144}.ui.inverted.red.button:focus,.ui.inverted.red.buttons .button:focus{background-color:#ff4335}.ui.inverted.red.active.button,.ui.inverted.red.buttons .active.button{background-color:#ff5144}.ui.inverted.red.button:active,.ui.inverted.red.buttons .button:active{background-color:#ff392b}.ui.inverted.red.basic.button,.ui.inverted.red.basic.buttons .button,.ui.inverted.red.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.red.basic.button:hover,.ui.inverted.red.basic.buttons .button:hover,.ui.inverted.red.buttons .basic.button:hover{box-shadow:0 0 0 2px #ff5144 inset!important;color:#FF695E!important}.ui.inverted.red.basic.button:focus,.ui.inverted.red.basic.buttons .button:focus{box-shadow:0 0 0 2px #ff4335 inset!important;color:#FF695E!important}.ui.inverted.red.basic.active.button,.ui.inverted.red.basic.buttons .active.button,.ui.inverted.red.buttons .basic.active.button{box-shadow:0 0 0 2px #ff5144 inset!important;color:#FF695E!important}.ui.inverted.red.basic.button:active,.ui.inverted.red.basic.buttons .button:active,.ui.inverted.red.buttons .basic.button:active{box-shadow:0 0 0 2px #ff392b inset!important;color:#FF695E!important}.ui.teal.button,.ui.teal.buttons .button{background-color:#00B5AD;color:#FFF;text-shadow:none;background-image:none}.ui.teal.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.teal.button:hover,.ui.teal.buttons .button:hover{background-color:#009c95;color:#FFF;text-shadow:none}.ui.teal.button:focus,.ui.teal.buttons .button:focus{background-color:#008c86;color:#FFF;text-shadow:none}.ui.teal.button:active,.ui.teal.buttons .button:active{background-color:#00827c;color:#FFF;text-shadow:none}.ui.teal.active.button,.ui.teal.button .active.button:active,.ui.teal.buttons .active.button,.ui.teal.buttons .active.button:active{background-color:#009c95;color:#FFF;text-shadow:none}.ui.basic.teal.button,.ui.basic.teal.buttons .button{box-shadow:0 0 0 1px #00B5AD inset!important;color:#00B5AD!important}.ui.basic.teal.button:hover,.ui.basic.teal.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #009c95 inset!important;color:#009c95!important}.ui.basic.teal.button:focus,.ui.basic.teal.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #008c86 inset!important;color:#009c95!important}.ui.basic.teal.active.button,.ui.basic.teal.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #009c95 inset!important;color:#00827c!important}.ui.basic.teal.button:active,.ui.basic.teal.buttons .button:active{box-shadow:0 0 0 1px #00827c inset!important;color:#00827c!important}.ui.buttons:not(.vertical)>.basic.teal.button:not(:first-child){margin-left:-1px}.ui.inverted.teal.button,.ui.inverted.teal.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #6DFFFF inset!important;color:#6DFFFF}.ui.inverted.teal.button.active,.ui.inverted.teal.button:active,.ui.inverted.teal.button:focus,.ui.inverted.teal.button:hover,.ui.inverted.teal.buttons .button.active,.ui.inverted.teal.buttons .button:active,.ui.inverted.teal.buttons .button:focus,.ui.inverted.teal.buttons .button:hover{box-shadow:none!important;color:rgba(0,0,0,.6)}.ui.inverted.teal.button:hover,.ui.inverted.teal.buttons .button:hover{background-color:#54ffff}.ui.inverted.teal.button:focus,.ui.inverted.teal.buttons .button:focus{background-color:#4ff}.ui.inverted.teal.active.button,.ui.inverted.teal.buttons .active.button{background-color:#54ffff}.ui.inverted.teal.button:active,.ui.inverted.teal.buttons .button:active{background-color:#3affff}.ui.inverted.teal.basic.button,.ui.inverted.teal.basic.buttons .button,.ui.inverted.teal.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.teal.basic.button:hover,.ui.inverted.teal.basic.buttons .button:hover,.ui.inverted.teal.buttons .basic.button:hover{box-shadow:0 0 0 2px #54ffff inset!important;color:#6DFFFF!important}.ui.inverted.teal.basic.button:focus,.ui.inverted.teal.basic.buttons .button:focus{box-shadow:0 0 0 2px #4ff inset!important;color:#6DFFFF!important}.ui.inverted.teal.basic.active.button,.ui.inverted.teal.basic.buttons .active.button,.ui.inverted.teal.buttons .basic.active.button{box-shadow:0 0 0 2px #54ffff inset!important;color:#6DFFFF!important}.ui.inverted.teal.basic.button:active,.ui.inverted.teal.basic.buttons .button:active,.ui.inverted.teal.buttons .basic.button:active{box-shadow:0 0 0 2px #3affff inset!important;color:#6DFFFF!important}.ui.olive.button,.ui.olive.buttons .button{background-color:#B5CC18;color:#FFF;text-shadow:none;background-image:none}.ui.olive.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.olive.button:hover,.ui.olive.buttons .button:hover{background-color:#a7bd0d;color:#FFF;text-shadow:none}.ui.olive.button:focus,.ui.olive.buttons .button:focus{background-color:#a0b605;color:#FFF;text-shadow:none}.ui.olive.button:active,.ui.olive.buttons .button:active{background-color:#8d9e13;color:#FFF;text-shadow:none}.ui.olive.active.button,.ui.olive.button .active.button:active,.ui.olive.buttons .active.button,.ui.olive.buttons .active.button:active{background-color:#aac109;color:#FFF;text-shadow:none}.ui.basic.olive.button,.ui.basic.olive.buttons .button{box-shadow:0 0 0 1px #B5CC18 inset!important;color:#B5CC18!important}.ui.basic.olive.button:hover,.ui.basic.olive.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #a7bd0d inset!important;color:#a7bd0d!important}.ui.basic.olive.button:focus,.ui.basic.olive.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #a0b605 inset!important;color:#a7bd0d!important}.ui.basic.olive.active.button,.ui.basic.olive.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #aac109 inset!important;color:#8d9e13!important}.ui.basic.olive.button:active,.ui.basic.olive.buttons .button:active{box-shadow:0 0 0 1px #8d9e13 inset!important;color:#8d9e13!important}.ui.buttons:not(.vertical)>.basic.olive.button:not(:first-child){margin-left:-1px}.ui.inverted.olive.button,.ui.inverted.olive.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #D9E778 inset!important;color:#D9E778}.ui.inverted.olive.button.active,.ui.inverted.olive.button:active,.ui.inverted.olive.button:focus,.ui.inverted.olive.button:hover,.ui.inverted.olive.buttons .button.active,.ui.inverted.olive.buttons .button:active,.ui.inverted.olive.buttons .button:focus,.ui.inverted.olive.buttons .button:hover{box-shadow:none!important;color:rgba(0,0,0,.6)}.ui.inverted.olive.button:hover,.ui.inverted.olive.buttons .button:hover{background-color:#d8ea5c}.ui.inverted.olive.button:focus,.ui.inverted.olive.buttons .button:focus{background-color:#daef47}.ui.inverted.olive.active.button,.ui.inverted.olive.buttons .active.button{background-color:#daed59}.ui.inverted.olive.button:active,.ui.inverted.olive.buttons .button:active{background-color:#cddf4d}.ui.inverted.olive.basic.button,.ui.inverted.olive.basic.buttons .button,.ui.inverted.olive.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.olive.basic.button:hover,.ui.inverted.olive.basic.buttons .button:hover,.ui.inverted.olive.buttons .basic.button:hover{box-shadow:0 0 0 2px #d8ea5c inset!important;color:#D9E778!important}.ui.inverted.olive.basic.button:focus,.ui.inverted.olive.basic.buttons .button:focus{box-shadow:0 0 0 2px #daef47 inset!important;color:#D9E778!important}.ui.inverted.olive.basic.active.button,.ui.inverted.olive.basic.buttons .active.button,.ui.inverted.olive.buttons .basic.active.button{box-shadow:0 0 0 2px #daed59 inset!important;color:#D9E778!important}.ui.inverted.olive.basic.button:active,.ui.inverted.olive.basic.buttons .button:active,.ui.inverted.olive.buttons .basic.button:active{box-shadow:0 0 0 2px #cddf4d inset!important;color:#D9E778!important}.ui.yellow.button,.ui.yellow.buttons .button{background-color:#FBBD08;color:#FFF;text-shadow:none;background-image:none}.ui.yellow.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.yellow.button:hover,.ui.yellow.buttons .button:hover{background-color:#eaae00;color:#FFF;text-shadow:none}.ui.yellow.button:focus,.ui.yellow.buttons .button:focus{background-color:#daa300;color:#FFF;text-shadow:none}.ui.yellow.button:active,.ui.yellow.buttons .button:active{background-color:#cd9903;color:#FFF;text-shadow:none}.ui.yellow.active.button,.ui.yellow.button .active.button:active,.ui.yellow.buttons .active.button,.ui.yellow.buttons .active.button:active{background-color:#eaae00;color:#FFF;text-shadow:none}.ui.basic.yellow.button,.ui.basic.yellow.buttons .button{box-shadow:0 0 0 1px #FBBD08 inset!important;color:#FBBD08!important}.ui.basic.yellow.button:hover,.ui.basic.yellow.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #eaae00 inset!important;color:#eaae00!important}.ui.basic.yellow.button:focus,.ui.basic.yellow.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #daa300 inset!important;color:#eaae00!important}.ui.basic.yellow.active.button,.ui.basic.yellow.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #eaae00 inset!important;color:#cd9903!important}.ui.basic.yellow.button:active,.ui.basic.yellow.buttons .button:active{box-shadow:0 0 0 1px #cd9903 inset!important;color:#cd9903!important}.ui.buttons:not(.vertical)>.basic.yellow.button:not(:first-child){margin-left:-1px}.ui.inverted.yellow.button,.ui.inverted.yellow.buttons .button{background-color:transparent;box-shadow:0 0 0 2px #FFE21F inset!important;color:#FFE21F}.ui.inverted.yellow.button.active,.ui.inverted.yellow.button:active,.ui.inverted.yellow.button:focus,.ui.inverted.yellow.button:hover,.ui.inverted.yellow.buttons .button.active,.ui.inverted.yellow.buttons .button:active,.ui.inverted.yellow.buttons .button:focus,.ui.inverted.yellow.buttons .button:hover{box-shadow:none!important;color:rgba(0,0,0,.6)}.ui.inverted.yellow.button:hover,.ui.inverted.yellow.buttons .button:hover{background-color:#ffdf05}.ui.inverted.yellow.button:focus,.ui.inverted.yellow.buttons .button:focus{background-color:#f5d500}.ui.inverted.yellow.active.button,.ui.inverted.yellow.buttons .active.button{background-color:#ffdf05}.ui.inverted.yellow.button:active,.ui.inverted.yellow.buttons .button:active{background-color:#ebcd00}.ui.inverted.yellow.basic.button,.ui.inverted.yellow.basic.buttons .button,.ui.inverted.yellow.buttons .basic.button{background-color:transparent;box-shadow:0 0 0 2px rgba(255,255,255,.5) inset!important;color:#FFF!important}.ui.inverted.yellow.basic.button:hover,.ui.inverted.yellow.basic.buttons .button:hover,.ui.inverted.yellow.buttons .basic.button:hover{box-shadow:0 0 0 2px #ffdf05 inset!important;color:#FFE21F!important}.ui.inverted.yellow.basic.button:focus,.ui.inverted.yellow.basic.buttons .button:focus{box-shadow:0 0 0 2px #f5d500 inset!important;color:#FFE21F!important}.ui.inverted.yellow.basic.active.button,.ui.inverted.yellow.basic.buttons .active.button,.ui.inverted.yellow.buttons .basic.active.button{box-shadow:0 0 0 2px #ffdf05 inset!important;color:#FFE21F!important}.ui.inverted.yellow.basic.button:active,.ui.inverted.yellow.basic.buttons .button:active,.ui.inverted.yellow.buttons .basic.button:active{box-shadow:0 0 0 2px #ebcd00 inset!important;color:#FFE21F!important}.ui.primary.button,.ui.primary.buttons .button{background-color:#2185D0;color:#FFF;text-shadow:none;background-image:none}.ui.primary.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.primary.button:hover,.ui.primary.buttons .button:hover{background-color:#1678c2;color:#FFF;text-shadow:none}.ui.primary.button:focus,.ui.primary.buttons .button:focus{background-color:#0d71bb;color:#FFF;text-shadow:none}.ui.primary.button:active,.ui.primary.buttons .button:active{background-color:#1a69a4;color:#FFF;text-shadow:none}.ui.primary.active.button,.ui.primary.button .active.button:active,.ui.primary.buttons .active.button,.ui.primary.buttons .active.button:active{background-color:#1279c6;color:#FFF;text-shadow:none}.ui.basic.primary.button,.ui.basic.primary.buttons .button{box-shadow:0 0 0 1px #2185D0 inset!important;color:#2185D0!important}.ui.basic.primary.button:hover,.ui.basic.primary.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #1678c2 inset!important;color:#1678c2!important}.ui.basic.primary.button:focus,.ui.basic.primary.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #0d71bb inset!important;color:#1678c2!important}.ui.basic.primary.active.button,.ui.basic.primary.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #1279c6 inset!important;color:#1a69a4!important}.ui.basic.primary.button:active,.ui.basic.primary.buttons .button:active{box-shadow:0 0 0 1px #1a69a4 inset!important;color:#1a69a4!important}.ui.secondary.button,.ui.secondary.buttons .button{background-color:#1B1C1D;color:#FFF;text-shadow:none;background-image:none}.ui.secondary.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.secondary.button:hover,.ui.secondary.buttons .button:hover{background-color:#27292a;color:#FFF;text-shadow:none}.ui.secondary.button:focus,.ui.secondary.buttons .button:focus{background-color:#2e3032;color:#FFF;text-shadow:none}.ui.secondary.button:active,.ui.secondary.buttons .button:active{background-color:#343637;color:#FFF;text-shadow:none}.ui.secondary.active.button,.ui.secondary.button .active.button:active,.ui.secondary.buttons .active.button,.ui.secondary.buttons .active.button:active{background-color:#27292a;color:#FFF;text-shadow:none}.ui.basic.secondary.button,.ui.basic.secondary.buttons .button{box-shadow:0 0 0 1px #1B1C1D inset!important;color:#1B1C1D!important}.ui.basic.secondary.button:hover,.ui.basic.secondary.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #27292a inset!important;color:#27292a!important}.ui.basic.secondary.button:focus,.ui.basic.secondary.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #2e3032 inset!important;color:#27292a!important}.ui.basic.secondary.active.button,.ui.basic.secondary.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #27292a inset!important;color:#343637!important}.ui.basic.secondary.button:active,.ui.basic.secondary.buttons .button:active{box-shadow:0 0 0 1px #343637 inset!important;color:#343637!important}.ui.positive.button,.ui.positive.buttons .button{background-color:#21BA45;color:#FFF;text-shadow:none;background-image:none}.ui.positive.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.positive.button:hover,.ui.positive.buttons .button:hover{background-color:#16ab39;color:#FFF;text-shadow:none}.ui.positive.button:focus,.ui.positive.buttons .button:focus{background-color:#0ea432;color:#FFF;text-shadow:none}.ui.positive.button:active,.ui.positive.buttons .button:active{background-color:#198f35;color:#FFF;text-shadow:none}.ui.positive.active.button,.ui.positive.button .active.button:active,.ui.positive.buttons .active.button,.ui.positive.buttons .active.button:active{background-color:#13ae38;color:#FFF;text-shadow:none}.ui.basic.positive.button,.ui.basic.positive.buttons .button{box-shadow:0 0 0 1px #21BA45 inset!important;color:#21BA45!important}.ui.basic.positive.button:hover,.ui.basic.positive.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #16ab39 inset!important;color:#16ab39!important}.ui.basic.positive.button:focus,.ui.basic.positive.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #0ea432 inset!important;color:#16ab39!important}.ui.basic.positive.active.button,.ui.basic.positive.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #13ae38 inset!important;color:#198f35!important}.ui.basic.positive.button:active,.ui.basic.positive.buttons .button:active{box-shadow:0 0 0 1px #198f35 inset!important;color:#198f35!important}.ui.negative.button,.ui.negative.buttons .button{background-color:#DB2828;color:#FFF;text-shadow:none;background-image:none}.ui.negative.button{box-shadow:0 0 0 0 rgba(34,36,38,.15) inset}.ui.negative.button:hover,.ui.negative.buttons .button:hover{background-color:#d01919;color:#FFF;text-shadow:none}.ui.negative.button:focus,.ui.negative.buttons .button:focus{background-color:#ca1010;color:#FFF;text-shadow:none}.ui.negative.button:active,.ui.negative.buttons .button:active{background-color:#b21e1e;color:#FFF;text-shadow:none}.ui.negative.active.button,.ui.negative.button .active.button:active,.ui.negative.buttons .active.button,.ui.negative.buttons .active.button:active{background-color:#d41515;color:#FFF;text-shadow:none}.ui.basic.negative.button,.ui.basic.negative.buttons .button{box-shadow:0 0 0 1px #DB2828 inset!important;color:#DB2828!important}.ui.basic.negative.button:hover,.ui.basic.negative.buttons .button:hover{background:0 0!important;box-shadow:0 0 0 1px #d01919 inset!important;color:#d01919!important}.ui.basic.negative.button:focus,.ui.basic.negative.buttons .button:focus{background:0 0!important;box-shadow:0 0 0 1px #ca1010 inset!important;color:#d01919!important}.ui.basic.negative.active.button,.ui.basic.negative.buttons .active.button{background:0 0!important;box-shadow:0 0 0 1px #d41515 inset!important;color:#b21e1e!important}.ui.basic.negative.button:active,.ui.basic.negative.buttons .button:active{box-shadow:0 0 0 1px #b21e1e inset!important;color:#b21e1e!important}.ui.buttons:not(.vertical)>.basic.primary.button:not(:first-child){margin-left:-1px}.ui.buttons{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;font-size:0;vertical-align:baseline;margin:0 .25em 0 0}.ui.buttons:not(.basic):not(.inverted){box-shadow:none}.ui.buttons:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ui.buttons .button{-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;border-radius:0;margin:0}.ui.buttons:not(.basic):not(.inverted)>.button,.ui.buttons>.ui.button:not(.basic):not(.inverted){box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(34,36,38,.15) inset}.ui.buttons .button:first-child{border-left:none;margin-left:0;border-top-left-radius:.28571429rem;border-bottom-left-radius:.28571429rem}.ui.buttons .button:last-child{border-top-right-radius:.28571429rem;border-bottom-right-radius:.28571429rem}.ui.vertical.buttons{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.ui.vertical.buttons .button{display:block;float:none;width:100%;margin:0;box-shadow:none;border-radius:0}.ui.vertical.buttons .button:first-child{border-top-left-radius:.28571429rem;border-top-right-radius:.28571429rem}.ui.vertical.buttons .button:last-child{margin-bottom:0;border-bottom-left-radius:.28571429rem;border-bottom-right-radius:.28571429rem}.ui.vertical.buttons .button:only-child{border-radius:.28571429rem}.ui.container{display:block;max-width:100%!important}@media only screen and (max-width:767px){.ui.container{width:auto!important;margin-left:1em!important;margin-right:1em!important}.ui.grid.container,.ui.relaxed.grid.container,.ui.very.relaxed.grid.container{width:auto!important}}@media only screen and (min-width:768px) and (max-width:991px){.ui.container{width:723px;margin-left:auto!important;margin-right:auto!important}.ui.grid.container{width:calc(723px + 2rem)!important}.ui.relaxed.grid.container{width:calc(723px + 3rem)!important}.ui.very.relaxed.grid.container{width:calc(723px + 5rem)!important}}@media only screen and (min-width:992px) and (max-width:1199px){.ui.container{width:933px;margin-left:auto!important;margin-right:auto!important}.ui.grid.container{width:calc(933px + 2rem)!important}.ui.relaxed.grid.container{width:calc(933px + 3rem)!important}.ui.very.relaxed.grid.container{width:calc(933px + 5rem)!important}}@media only screen and (min-width:1200px){.ui.container{width:1127px;margin-left:auto!important;margin-right:auto!important}.ui.grid.container{width:calc(1127px + 2rem)!important}.ui.relaxed.grid.container{width:calc(1127px + 3rem)!important}.ui.very.relaxed.grid.container{width:calc(1127px + 5rem)!important}}.ui.text.container{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;max-width:700px!important;line-height:1.5;font-size:1.14285714rem}.ui.fluid.container{width:100%}.ui[class*="left aligned"].container{text-align:left}.ui[class*="center aligned"].container{text-align:center}.ui[class*="right aligned"].container{text-align:right}.ui.justified.container{text-align:justify;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}.ui.divider{margin:1rem 0;line-height:1;height:0;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:rgba(0,0,0,.85);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;font-size:1rem}.ui.divider:not(.vertical):not(.horizontal){border-top:1px solid rgba(34,36,38,.15);border-bottom:1px solid rgba(255,255,255,.1)}.ui.grid>.column+.divider,.ui.grid>.row>.column+.divider{left:auto}.ui.horizontal.divider{display:table;white-space:nowrap;height:auto;margin:'';line-height:1;text-align:center}.ui.horizontal.divider:after,.ui.horizontal.divider:before{content:'';display:table-cell;position:relative;top:50%;width:50%;background-repeat:no-repeat;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAACCAYAAACuTHuKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1OThBRDY4OUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1OThBRDY4QUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjU5OEFENjg3Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU5OEFENjg4Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+VU513gAAADVJREFUeNrs0DENACAQBDBIWLGBJQby/mUcJn5sJXQmOQMAAAAAAJqt+2prAAAAAACg2xdgANk6BEVuJgyMAAAAAElFTkSuQmCC)}.ui.horizontal.divider:before{background-position:right 1em top 50%}.ui.horizontal.divider:after{background-position:left 1em top 50%}.ui.vertical.divider{position:absolute;z-index:2;top:50%;left:50%;margin:0;padding:0;width:auto;height:50%;line-height:0;text-align:center;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.ui.vertical.divider:after,.ui.vertical.divider:before{position:absolute;left:50%;content:'';z-index:3;border-left:1px solid rgba(34,36,38,.15);border-right:1px solid rgba(255,255,255,.1);width:0;height:calc(100% - 1rem)}.ui.vertical.divider:before{top:-100%}.ui.vertical.divider:after{top:auto;bottom:0}@media only screen and (max-width:767px){.ui.grid .stackable.row .ui.vertical.divider,.ui.stackable.grid .ui.vertical.divider{display:table;white-space:nowrap;height:auto;margin:'';overflow:hidden;line-height:1;text-align:center;position:static;top:0;left:0;-webkit-transform:none;transform:none}.ui.grid .stackable.row .ui.vertical.divider:after,.ui.grid .stackable.row .ui.vertical.divider:before,.ui.stackable.grid .ui.vertical.divider:after,.ui.stackable.grid .ui.vertical.divider:before{left:0;border-left:none;border-right:none;content:'';display:table-cell;position:relative;top:50%;width:50%;background-repeat:no-repeat;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAACCAYAAACuTHuKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1OThBRDY4OUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1OThBRDY4QUNDMTYxMUU0OUE3NUVGOEJDMzMzMjE2NyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjU5OEFENjg3Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjU5OEFENjg4Q0MxNjExRTQ5QTc1RUY4QkMzMzMyMTY3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+VU513gAAADVJREFUeNrs0DENACAQBDBIWLGBJQby/mUcJn5sJXQmOQMAAAAAAJqt+2prAAAAAACg2xdgANk6BEVuJgyMAAAAAElFTkSuQmCC)}.ui.grid .stackable.row .ui.vertical.divider:before,.ui.stackable.grid .ui.vertical.divider:before{background-position:right 1em top 50%}.ui.grid .stackable.row .ui.vertical.divider:after,.ui.stackable.grid .ui.vertical.divider:after{background-position:left 1em top 50%}}.ui.divider>.icon{margin:0;font-size:1rem;height:1em;vertical-align:middle}.ui.hidden.divider{border-color:transparent!important}.ui.hidden.divider:after,.ui.hidden.divider:before{display:none}.ui.divider.inverted,.ui.horizontal.inverted.divider,.ui.vertical.inverted.divider{color:#FFF}.ui.divider.inverted,.ui.divider.inverted:after,.ui.divider.inverted:before{border-top-color:rgba(34,36,38,.15)!important;border-left-color:rgba(34,36,38,.15)!important;border-bottom-color:rgba(255,255,255,.15)!important;border-right-color:rgba(255,255,255,.15)!important}.ui.fitted.divider{margin:0}.ui.clearing.divider{clear:both}.ui.section.divider{margin-top:2rem;margin-bottom:2rem}i.flag:not(.icon){display:inline-block;width:16px;height:11px;line-height:11px;vertical-align:baseline;margin:0 .5em 0 0;text-decoration:inherit;speak:none;font-smoothing:antialiased;-webkit-backface-visibility:hidden;backface-visibility:hidden}i.flag:not(.icon):before{display:inline-block;content:'';background:url(themes/default/assets/images/flags.png) -108px -1976px no-repeat;width:16px;height:11px}i.flag.ad:before,i.flag.andorra:before{background-position:0 0}i.flag.ae:before,i.flag.uae:before,i.flag.united.arab.emirates:before{background-position:0 -26px}i.flag.af:before,i.flag.afghanistan:before{background-position:0 -52px}i.flag.ag:before,i.flag.antigua:before{background-position:0 -78px}i.flag.ai:before,i.flag.anguilla:before{background-position:0 -104px}i.flag.al:before,i.flag.albania:before{background-position:0 -130px}i.flag.am:before,i.flag.armenia:before{background-position:0 -156px}i.flag.an:before,i.flag.netherlands.antilles:before{background-position:0 -182px}i.flag.angola:before,i.flag.ao:before{background-position:0 -208px}i.flag.ar:before,i.flag.argentina:before{background-position:0 -234px}i.flag.american.samoa:before,i.flag.as:before{background-position:0 -260px}i.flag.at:before,i.flag.austria:before{background-position:0 -286px}i.flag.au:before,i.flag.australia:before{background-position:0 -312px}i.flag.aruba:before,i.flag.aw:before{background-position:0 -338px}i.flag.aland.islands:before,i.flag.ax:before{background-position:0 -364px}i.flag.az:before,i.flag.azerbaijan:before{background-position:0 -390px}i.flag.ba:before,i.flag.bosnia:before{background-position:0 -416px}i.flag.barbados:before,i.flag.bb:before{background-position:0 -442px}i.flag.bangladesh:before,i.flag.bd:before{background-position:0 -468px}i.flag.be:before,i.flag.belgium:before{background-position:0 -494px}i.flag.bf:before,i.flag.burkina.faso:before{background-position:0 -520px}i.flag.bg:before,i.flag.bulgaria:before{background-position:0 -546px}i.flag.bahrain:before,i.flag.bh:before{background-position:0 -572px}i.flag.bi:before,i.flag.burundi:before{background-position:0 -598px}i.flag.benin:before,i.flag.bj:before{background-position:0 -624px}i.flag.bermuda:before,i.flag.bm:before{background-position:0 -650px}i.flag.bn:before,i.flag.brunei:before{background-position:0 -676px}i.flag.bo:before,i.flag.bolivia:before{background-position:0 -702px}i.flag.br:before,i.flag.brazil:before{background-position:0 -728px}i.flag.bahamas:before,i.flag.bs:before{background-position:0 -754px}i.flag.bhutan:before,i.flag.bt:before{background-position:0 -780px}i.flag.bouvet.island:before,i.flag.bv:before{background-position:0 -806px}i.flag.botswana:before,i.flag.bw:before{background-position:0 -832px}i.flag.belarus:before,i.flag.by:before{background-position:0 -858px}i.flag.belize:before,i.flag.bz:before{background-position:0 -884px}i.flag.ca:before,i.flag.canada:before{background-position:0 -910px}i.flag.cc:before,i.flag.cocos.islands:before{background-position:0 -962px}i.flag.cd:before,i.flag.congo:before{background-position:0 -988px}i.flag.central.african.republic:before,i.flag.cf:before{background-position:0 -1014px}i.flag.cg:before,i.flag.congo.brazzaville:before{background-position:0 -1040px}i.flag.ch:before,i.flag.switzerland:before{background-position:0 -1066px}i.flag.ci:before,i.flag.cote.divoire:before{background-position:0 -1092px}i.flag.ck:before,i.flag.cook.islands:before{background-position:0 -1118px}i.flag.chile:before,i.flag.cl:before{background-position:0 -1144px}i.flag.cameroon:before,i.flag.cm:before{background-position:0 -1170px}i.flag.china:before,i.flag.cn:before{background-position:0 -1196px}i.flag.co:before,i.flag.colombia:before{background-position:0 -1222px}i.flag.costa.rica:before,i.flag.cr:before{background-position:0 -1248px}i.flag.cs:before,i.flag.serbia:before{background-position:0 -1274px}i.flag.cu:before,i.flag.cuba:before{background-position:0 -1300px}i.flag.cape.verde:before,i.flag.cv:before{background-position:0 -1326px}i.flag.christmas.island:before,i.flag.cx:before{background-position:0 -1352px}i.flag.cy:before,i.flag.cyprus:before{background-position:0 -1378px}i.flag.cz:before,i.flag.czech.republic:before{background-position:0 -1404px}i.flag.de:before,i.flag.germany:before{background-position:0 -1430px}i.flag.dj:before,i.flag.djibouti:before{background-position:0 -1456px}i.flag.denmark:before,i.flag.dk:before{background-position:0 -1482px}i.flag.dm:before,i.flag.dominica:before{background-position:0 -1508px}i.flag.do:before,i.flag.dominican.republic:before{background-position:0 -1534px}i.flag.algeria:before,i.flag.dz:before{background-position:0 -1560px}i.flag.ec:before,i.flag.ecuador:before{background-position:0 -1586px}i.flag.ee:before,i.flag.estonia:before{background-position:0 -1612px}i.flag.eg:before,i.flag.egypt:before{background-position:0 -1638px}i.flag.eh:before,i.flag.western.sahara:before{background-position:0 -1664px}i.flag.er:before,i.flag.eritrea:before{background-position:0 -1716px}i.flag.es:before,i.flag.spain:before{background-position:0 -1742px}i.flag.et:before,i.flag.ethiopia:before{background-position:0 -1768px}i.flag.eu:before,i.flag.european.union:before{background-position:0 -1794px}i.flag.fi:before,i.flag.finland:before{background-position:0 -1846px}i.flag.fiji:before,i.flag.fj:before{background-position:0 -1872px}i.flag.falkland.islands:before,i.flag.fk:before{background-position:0 -1898px}i.flag.fm:before,i.flag.micronesia:before{background-position:0 -1924px}i.flag.faroe.islands:before,i.flag.fo:before{background-position:0 -1950px}i.flag.fr:before,i.flag.france:before{background-position:0 -1976px}i.flag.ga:before,i.flag.gabon:before{background-position:-36px 0}i.flag.gb:before,i.flag.united.kingdom:before{background-position:-36px -26px}i.flag.gd:before,i.flag.grenada:before{background-position:-36px -52px}i.flag.ge:before,i.flag.georgia:before{background-position:-36px -78px}i.flag.french.guiana:before,i.flag.gf:before{background-position:-36px -104px}i.flag.gh:before,i.flag.ghana:before{background-position:-36px -130px}i.flag.gi:before,i.flag.gibraltar:before{background-position:-36px -156px}i.flag.gl:before,i.flag.greenland:before{background-position:-36px -182px}i.flag.gambia:before,i.flag.gm:before{background-position:-36px -208px}i.flag.gn:before,i.flag.guinea:before{background-position:-36px -234px}i.flag.gp:before,i.flag.guadeloupe:before{background-position:-36px -260px}i.flag.equatorial.guinea:before,i.flag.gq:before{background-position:-36px -286px}i.flag.gr:before,i.flag.greece:before{background-position:-36px -312px}i.flag.gs:before,i.flag.sandwich.islands:before{background-position:-36px -338px}i.flag.gt:before,i.flag.guatemala:before{background-position:-36px -364px}i.flag.gu:before,i.flag.guam:before{background-position:-36px -390px}i.flag.guinea-bissau:before,i.flag.gw:before{background-position:-36px -416px}i.flag.guyana:before,i.flag.gy:before{background-position:-36px -442px}i.flag.hk:before,i.flag.hong.kong:before{background-position:-36px -468px}i.flag.heard.island:before,i.flag.hm:before{background-position:-36px -494px}i.flag.hn:before,i.flag.honduras:before{background-position:-36px -520px}i.flag.croatia:before,i.flag.hr:before{background-position:-36px -546px}i.flag.haiti:before,i.flag.ht:before{background-position:-36px -572px}i.flag.hu:before,i.flag.hungary:before{background-position:-36px -598px}i.flag.id:before,i.flag.indonesia:before{background-position:-36px -624px}i.flag.ie:before,i.flag.ireland:before{background-position:-36px -650px}i.flag.il:before,i.flag.israel:before{background-position:-36px -676px}i.flag.in:before,i.flag.india:before{background-position:-36px -702px}i.flag.indian.ocean.territory:before,i.flag.io:before{background-position:-36px -728px}i.flag.iq:before,i.flag.iraq:before{background-position:-36px -754px}i.flag.ir:before,i.flag.iran:before{background-position:-36px -780px}i.flag.iceland:before,i.flag.is:before{background-position:-36px -806px}i.flag.it:before,i.flag.italy:before{background-position:-36px -832px}i.flag.jamaica:before,i.flag.jm:before{background-position:-36px -858px}i.flag.jo:before,i.flag.jordan:before{background-position:-36px -884px}i.flag.japan:before,i.flag.jp:before{background-position:-36px -910px}i.flag.ke:before,i.flag.kenya:before{background-position:-36px -936px}i.flag.kg:before,i.flag.kyrgyzstan:before{background-position:-36px -962px}i.flag.cambodia:before,i.flag.kh:before{background-position:-36px -988px}i.flag.ki:before,i.flag.kiribati:before{background-position:-36px -1014px}i.flag.comoros:before,i.flag.km:before{background-position:-36px -1040px}i.flag.kn:before,i.flag.saint.kitts.and.nevis:before{background-position:-36px -1066px}i.flag.kp:before,i.flag.north.korea:before{background-position:-36px -1092px}i.flag.kr:before,i.flag.south.korea:before{background-position:-36px -1118px}i.flag.kuwait:before,i.flag.kw:before{background-position:-36px -1144px}i.flag.cayman.islands:before,i.flag.ky:before{background-position:-36px -1170px}i.flag.kazakhstan:before,i.flag.kz:before{background-position:-36px -1196px}i.flag.la:before,i.flag.laos:before{background-position:-36px -1222px}i.flag.lb:before,i.flag.lebanon:before{background-position:-36px -1248px}i.flag.lc:before,i.flag.saint.lucia:before{background-position:-36px -1274px}i.flag.li:before,i.flag.liechtenstein:before{background-position:-36px -1300px}i.flag.lk:before,i.flag.sri.lanka:before{background-position:-36px -1326px}i.flag.liberia:before,i.flag.lr:before{background-position:-36px -1352px}i.flag.lesotho:before,i.flag.ls:before{background-position:-36px -1378px}i.flag.lithuania:before,i.flag.lt:before{background-position:-36px -1404px}i.flag.lu:before,i.flag.luxembourg:before{background-position:-36px -1430px}i.flag.latvia:before,i.flag.lv:before{background-position:-36px -1456px}i.flag.libya:before,i.flag.ly:before{background-position:-36px -1482px}i.flag.ma:before,i.flag.morocco:before{background-position:-36px -1508px}i.flag.mc:before,i.flag.monaco:before{background-position:-36px -1534px}i.flag.md:before,i.flag.moldova:before{background-position:-36px -1560px}i.flag.me:before,i.flag.montenegro:before{background-position:-36px -1586px}i.flag.madagascar:before,i.flag.mg:before{background-position:-36px -1613px}i.flag.marshall.islands:before,i.flag.mh:before{background-position:-36px -1639px}i.flag.macedonia:before,i.flag.mk:before{background-position:-36px -1665px}i.flag.mali:before,i.flag.ml:before{background-position:-36px -1691px}i.flag.burma:before,i.flag.mm:before,i.flag.myanmar:before{background-position:-36px -1717px}i.flag.mn:before,i.flag.mongolia:before{background-position:-36px -1743px}i.flag.macau:before,i.flag.mo:before{background-position:-36px -1769px}i.flag.mp:before,i.flag.northern.mariana.islands:before{background-position:-36px -1795px}i.flag.martinique:before,i.flag.mq:before{background-position:-36px -1821px}i.flag.mauritania:before,i.flag.mr:before{background-position:-36px -1847px}i.flag.montserrat:before,i.flag.ms:before{background-position:-36px -1873px}i.flag.malta:before,i.flag.mt:before{background-position:-36px -1899px}i.flag.mauritius:before,i.flag.mu:before{background-position:-36px -1925px}i.flag.maldives:before,i.flag.mv:before{background-position:-36px -1951px}i.flag.malawi:before,i.flag.mw:before{background-position:-36px -1977px}i.flag.mexico:before,i.flag.mx:before{background-position:-72px 0}i.flag.malaysia:before,i.flag.my:before{background-position:-72px -26px}i.flag.mozambique:before,i.flag.mz:before{background-position:-72px -52px}i.flag.na:before,i.flag.namibia:before{background-position:-72px -78px}i.flag.nc:before,i.flag.new.caledonia:before{background-position:-72px -104px}i.flag.ne:before,i.flag.niger:before{background-position:-72px -130px}i.flag.nf:before,i.flag.norfolk.island:before{background-position:-72px -156px}i.flag.ng:before,i.flag.nigeria:before{background-position:-72px -182px}i.flag.ni:before,i.flag.nicaragua:before{background-position:-72px -208px}i.flag.netherlands:before,i.flag.nl:before{background-position:-72px -234px}i.flag.no:before,i.flag.norway:before{background-position:-72px -260px}i.flag.nepal:before,i.flag.np:before{background-position:-72px -286px}i.flag.nauru:before,i.flag.nr:before{background-position:-72px -312px}i.flag.niue:before,i.flag.nu:before{background-position:-72px -338px}i.flag.new.zealand:before,i.flag.nz:before{background-position:-72px -364px}i.flag.om:before,i.flag.oman:before{background-position:-72px -390px}i.flag.pa:before,i.flag.panama:before{background-position:-72px -416px}i.flag.pe:before,i.flag.peru:before{background-position:-72px -442px}i.flag.french.polynesia:before,i.flag.pf:before{background-position:-72px -468px}i.flag.new.guinea:before,i.flag.pg:before{background-position:-72px -494px}i.flag.ph:before,i.flag.philippines:before{background-position:-72px -520px}i.flag.pakistan:before,i.flag.pk:before{background-position:-72px -546px}i.flag.pl:before,i.flag.poland:before{background-position:-72px -572px}i.flag.pm:before,i.flag.saint.pierre:before{background-position:-72px -598px}i.flag.pitcairn.islands:before,i.flag.pn:before{background-position:-72px -624px}i.flag.pr:before,i.flag.puerto.rico:before{background-position:-72px -650px}i.flag.palestine:before,i.flag.ps:before{background-position:-72px -676px}i.flag.portugal:before,i.flag.pt:before{background-position:-72px -702px}i.flag.palau:before,i.flag.pw:before{background-position:-72px -728px}i.flag.paraguay:before,i.flag.py:before{background-position:-72px -754px}i.flag.qa:before,i.flag.qatar:before{background-position:-72px -780px}i.flag.re:before,i.flag.reunion:before{background-position:-72px -806px}i.flag.ro:before,i.flag.romania:before{background-position:-72px -832px}i.flag.rs:before,i.flag.serbia:before{background-position:-72px -858px}i.flag.ru:before,i.flag.russia:before{background-position:-72px -884px}i.flag.rw:before,i.flag.rwanda:before{background-position:-72px -910px}i.flag.sa:before,i.flag.saudi.arabia:before{background-position:-72px -936px}i.flag.sb:before,i.flag.solomon.islands:before{background-position:-72px -962px}i.flag.sc:before,i.flag.seychelles:before{background-position:-72px -988px}i.flag.gb.sct:before,i.flag.scotland:before{background-position:-72px -1014px}i.flag.sd:before,i.flag.sudan:before{background-position:-72px -1040px}i.flag.se:before,i.flag.sweden:before{background-position:-72px -1066px}i.flag.sg:before,i.flag.singapore:before{background-position:-72px -1092px}i.flag.saint.helena:before,i.flag.sh:before{background-position:-72px -1118px}i.flag.si:before,i.flag.slovenia:before{background-position:-72px -1144px}i.flag.jan.mayen:before,i.flag.sj:before,i.flag.svalbard:before{background-position:-72px -1170px}i.flag.sk:before,i.flag.slovakia:before{background-position:-72px -1196px}i.flag.sierra.leone:before,i.flag.sl:before{background-position:-72px -1222px}i.flag.san.marino:before,i.flag.sm:before{background-position:-72px -1248px}i.flag.senegal:before,i.flag.sn:before{background-position:-72px -1274px}i.flag.so:before,i.flag.somalia:before{background-position:-72px -1300px}i.flag.sr:before,i.flag.suriname:before{background-position:-72px -1326px}i.flag.sao.tome:before,i.flag.st:before{background-position:-72px -1352px}i.flag.el.salvador:before,i.flag.sv:before{background-position:-72px -1378px}i.flag.sy:before,i.flag.syria:before{background-position:-72px -1404px}i.flag.swaziland:before,i.flag.sz:before{background-position:-72px -1430px}i.flag.caicos.islands:before,i.flag.tc:before{background-position:-72px -1456px}i.flag.chad:before,i.flag.td:before{background-position:-72px -1482px}i.flag.french.territories:before,i.flag.tf:before{background-position:-72px -1508px}i.flag.tg:before,i.flag.togo:before{background-position:-72px -1534px}i.flag.th:before,i.flag.thailand:before{background-position:-72px -1560px}i.flag.tajikistan:before,i.flag.tj:before{background-position:-72px -1586px}i.flag.tk:before,i.flag.tokelau:before{background-position:-72px -1612px}i.flag.timorleste:before,i.flag.tl:before{background-position:-72px -1638px}i.flag.tm:before,i.flag.turkmenistan:before{background-position:-72px -1664px}i.flag.tn:before,i.flag.tunisia:before{background-position:-72px -1690px}i.flag.to:before,i.flag.tonga:before{background-position:-72px -1716px}i.flag.tr:before,i.flag.turkey:before{background-position:-72px -1742px}i.flag.trinidad:before,i.flag.tt:before{background-position:-72px -1768px}i.flag.tuvalu:before,i.flag.tv:before{background-position:-72px -1794px}i.flag.taiwan:before,i.flag.tw:before{background-position:-72px -1820px}i.flag.tanzania:before,i.flag.tz:before{background-position:-72px -1846px}i.flag.ua:before,i.flag.ukraine:before{background-position:-72px -1872px}i.flag.ug:before,i.flag.uganda:before{background-position:-72px -1898px}i.flag.um:before,i.flag.us.minor.islands:before{background-position:-72px -1924px}i.flag.america:before,i.flag.united.states:before,i.flag.us:before{background-position:-72px -1950px}i.flag.uruguay:before,i.flag.uy:before{background-position:-72px -1976px}i.flag.uz:before,i.flag.uzbekistan:before{background-position:-108px 0}i.flag.va:before,i.flag.vatican.city:before{background-position:-108px -26px}i.flag.saint.vincent:before,i.flag.vc:before{background-position:-108px -52px}i.flag.ve:before,i.flag.venezuela:before{background-position:-108px -78px}i.flag.british.virgin.islands:before,i.flag.vg:before{background-position:-108px -104px}i.flag.us.virgin.islands:before,i.flag.vi:before{background-position:-108px -130px}i.flag.vietnam:before,i.flag.vn:before{background-position:-108px -156px}i.flag.vanuatu:before,i.flag.vu:before{background-position:-108px -182px}i.flag.gb.wls:before,i.flag.wales:before{background-position:-108px -208px}i.flag.wallis.and.futuna:before,i.flag.wf:before{background-position:-108px -234px}i.flag.samoa:before,i.flag.ws:before{background-position:-108px -260px}i.flag.ye:before,i.flag.yemen:before{background-position:-108px -286px}i.flag.mayotte:before,i.flag.yt:before{background-position:-108px -312px}i.flag.south.africa:before,i.flag.za:before{background-position:-108px -338px}i.flag.zambia:before,i.flag.zm:before{background-position:-108px -364px}i.flag.zimbabwe:before,i.flag.zw:before{background-position:-108px -390px}.ui.header{border:none;margin:calc(2rem - .14285em) 0 1rem;padding:0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:700;line-height:1.2857em;text-transform:none;color:rgba(0,0,0,.87)}.ui.header:first-child{margin-top:-.14285em}.ui.header:last-child{margin-bottom:0}.ui.header .sub.header{display:block;font-weight:400;padding:0;margin:0;line-height:1.2em;color:rgba(0,0,0,.6)}.ui.header>.icon{display:table-cell;opacity:1;font-size:1.5em;padding-top:.14285em;vertical-align:middle}.ui.header .icon:only-child{display:inline-block;padding:0;margin-right:.75rem}.ui.header>.image,.ui.header>img{display:inline-block;margin-top:.14285em;width:2.5em;height:auto;vertical-align:middle}.ui.header>.image:only-child,.ui.header>img:only-child{margin-right:.75rem}.ui.header .content{display:inline-block;vertical-align:top}.ui.header>.image+.content,.ui.header>img+.content{padding-left:.75rem;vertical-align:middle}.ui.header>.icon+.content{padding-left:.75rem;display:table-cell;vertical-align:middle}.ui.header .ui.label{font-size:'';margin-left:.5rem;vertical-align:middle}.ui.header+p{margin-top:0}h1.ui.header{font-size:2rem}h2.ui.header{font-size:1.714rem}h3.ui.header{font-size:1.28rem}h4.ui.header{font-size:1.071rem}h5.ui.header{font-size:1rem}h1.ui.header .sub.header,h2.ui.header .sub.header{font-size:1.14285714rem}h3.ui.header .sub.header,h4.ui.header .sub.header{font-size:1rem}h5.ui.header .sub.header{font-size:.92857143rem}.ui.huge.header{min-height:1em;font-size:2em}.ui.large.header{font-size:1.714em}.ui.medium.header{font-size:1.28em}.ui.small.header{font-size:1.071em}.ui.tiny.header{font-size:1em}.ui.huge.header .sub.header,.ui.large.header .sub.header{font-size:1.14285714rem}.ui.header .sub.header,.ui.small.header .sub.header{font-size:1rem}.ui.tiny.header .sub.header{font-size:.92857143rem}.ui.small.sub.header{font-size:.78571429em}.ui.sub.header{padding:0;margin-bottom:.14285714rem;font-weight:700;text-transform:uppercase;color:'';font-size:.85714286em}.ui.large.sub.header{font-size:.92857143em}.ui.huge.sub.header{font-size:1em}.ui.icon.header{display:inline-block;text-align:center;margin:2rem 0 1rem}.ui.icon.header:after{content:'';display:block;height:0;clear:both;visibility:hidden}.ui.icon.header:first-child{margin-top:0}.ui.icon.header .icon{float:none;display:block;width:auto;height:auto;line-height:1;padding:0;font-size:3em;margin:0 auto .5rem;opacity:1}.ui.icon.header .content{display:block;padding:0}.ui.icon.header .circular.icon,.ui.icon.header .square.icon{font-size:2em}.ui.block.icon.header .icon{margin-bottom:0}.ui.icon.header.aligned{margin-left:auto;margin-right:auto;display:block}.ui.disabled.header{opacity:.45}.ui.inverted.header{color:#FFF}.ui.inverted.header .sub.header{color:rgba(255,255,255,.8)}.ui.inverted.attached.header{background:-webkit-linear-gradient(transparent,rgba(0,0,0,.05)) #545454;background:linear-gradient(transparent,rgba(0,0,0,.05)) #545454;box-shadow:none;border-color:transparent}.ui.inverted.block.header{background:-webkit-linear-gradient(transparent,rgba(0,0,0,.05)) #545454;background:linear-gradient(transparent,rgba(0,0,0,.05)) #545454;box-shadow:none;border-bottom:none}.ui.red.header{color:#DB2828!important}a.ui.red.header:hover{color:#d01919!important}.ui.red.dividing.header{border-bottom:2px solid #DB2828}.ui.inverted.red.header{color:#FF695E!important}a.ui.inverted.red.header:hover{color:#ff5144!important}.ui.orange.header{color:#F2711C!important}a.ui.orange.header:hover{color:#f26202!important}.ui.orange.dividing.header{border-bottom:2px solid #F2711C}.ui.inverted.orange.header{color:#FF851B!important}a.ui.inverted.orange.header:hover{color:#ff7701!important}.ui.olive.header{color:#B5CC18!important}a.ui.olive.header:hover{color:#a7bd0d!important}.ui.olive.dividing.header{border-bottom:2px solid #B5CC18}.ui.inverted.olive.header{color:#D9E778!important}a.ui.inverted.olive.header:hover{color:#d8ea5c!important}.ui.yellow.header{color:#FBBD08!important}a.ui.yellow.header:hover{color:#eaae00!important}.ui.yellow.dividing.header{border-bottom:2px solid #FBBD08}.ui.inverted.yellow.header{color:#FFE21F!important}a.ui.inverted.yellow.header:hover{color:#ffdf05!important}.ui.green.header{color:#21BA45!important}a.ui.green.header:hover{color:#16ab39!important}.ui.green.dividing.header{border-bottom:2px solid #21BA45}.ui.inverted.green.header{color:#2ECC40!important}a.ui.inverted.green.header:hover{color:#22be34!important}.ui.teal.header{color:#00B5AD!important}a.ui.teal.header:hover{color:#009c95!important}.ui.teal.dividing.header{border-bottom:2px solid #00B5AD}.ui.inverted.teal.header{color:#6DFFFF!important}a.ui.inverted.teal.header:hover{color:#54ffff!important}.ui.blue.header{color:#2185D0!important}a.ui.blue.header:hover{color:#1678c2!important}.ui.blue.dividing.header{border-bottom:2px solid #2185D0}.ui.inverted.blue.header{color:#54C8FF!important}a.ui.inverted.blue.header:hover{color:#3ac0ff!important}.ui.violet.header{color:#6435C9!important}a.ui.violet.header:hover{color:#5829bb!important}.ui.violet.dividing.header{border-bottom:2px solid #6435C9}.ui.inverted.violet.header{color:#A291FB!important}a.ui.inverted.violet.header:hover{color:#8a73ff!important}.ui.purple.header{color:#A333C8!important}a.ui.purple.header:hover{color:#9627ba!important}.ui.purple.dividing.header{border-bottom:2px solid #A333C8}.ui.inverted.purple.header{color:#DC73FF!important}a.ui.inverted.purple.header:hover{color:#d65aff!important}.ui.pink.header{color:#E03997!important}a.ui.pink.header:hover{color:#e61a8d!important}.ui.pink.dividing.header{border-bottom:2px solid #E03997}.ui.inverted.pink.header{color:#FF8EDF!important}a.ui.inverted.pink.header:hover{color:#ff74d8!important}.ui.brown.header{color:#A5673F!important}a.ui.brown.header:hover{color:#975b33!important}.ui.brown.dividing.header{border-bottom:2px solid #A5673F}.ui.inverted.brown.header{color:#D67C1C!important}a.ui.inverted.brown.header:hover{color:#c86f11!important}.ui.grey.header{color:#767676!important}a.ui.grey.header:hover{color:#838383!important}.ui.grey.dividing.header{border-bottom:2px solid #767676}.ui.inverted.grey.header{color:#DCDDDE!important}a.ui.inverted.grey.header:hover{color:#cfd0d2!important}.ui.left.aligned.header{text-align:left}.ui.right.aligned.header{text-align:right}.ui.center.aligned.header,.ui.centered.header{text-align:center}.ui.justified.header{text-align:justify}.ui.justified.header:after{display:inline-block;content:'';width:100%}.ui.floated.header,.ui[class*="left floated"].header{float:left;margin-top:0;margin-right:.5em}.ui[class*="right floated"].header{float:right;margin-top:0;margin-left:.5em}.ui.fitted.header{padding:0}.ui.dividing.header{padding-bottom:.21428571rem;border-bottom:1px solid rgba(34,36,38,.15)}.ui.dividing.header .sub.header{padding-bottom:.21428571rem}.ui.dividing.header .icon{margin-bottom:0}.ui.inverted.dividing.header{border-bottom-color:rgba(255,255,255,.1)}.ui.block.header{background:#F3F4F5;padding:.78571429rem 1rem;box-shadow:none;border:1px solid #D4D4D5;border-radius:.28571429rem}.ui.tiny.block.header{font-size:.85714286rem}.ui.small.block.header{font-size:.92857143rem}.ui.block.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6){font-size:1rem}.ui.large.block.header{font-size:1.14285714rem}.ui.huge.block.header{font-size:1.42857143rem}.ui.attached.header{background:#FFF;padding:.78571429rem 1rem;margin-left:-1px;margin-right:-1px;box-shadow:none;border:1px solid #D4D4D5}.ui.attached.block.header{background:#F3F4F5}.ui.attached:not(.top):not(.bottom).header{margin-top:0;margin-bottom:0;border-top:none;border-radius:0}.ui.top.attached.header{margin-bottom:0;border-radius:.28571429rem .28571429rem 0 0}.ui.bottom.attached.header{margin-top:0;border-top:none;border-radius:0 0 .28571429rem .28571429rem}.ui.tiny.attached.header{font-size:.85714286em}.ui.small.attached.header{font-size:.92857143em}.ui.attached.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6){font-size:1em}.ui.large.attached.header{font-size:1.14285714em}.ui.huge.attached.header{font-size:1.42857143em}.ui.header:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6){font-size:1.28em}@font-face{font-family:Icons;src:url(themes/default/assets/fonts/icons.eot);src:url(themes/default/assets/fonts/icons.eot?#iefix) format('embedded-opentype'),url(themes/default/assets/fonts/icons.woff2) format('woff2'),url(themes/default/assets/fonts/icons.woff) format('woff'),url(themes/default/assets/fonts/icons.ttf) format('truetype'),url(themes/default/assets/fonts/icons.svg#icons) format('svg');font-style:normal;font-weight:400;font-variant:normal;text-decoration:inherit;text-transform:none}i.icon{display:inline-block;opacity:1;margin:0 .25rem 0 0;width:1.18em;height:1em;font-family:Icons;font-style:normal;font-weight:400;text-decoration:inherit;text-align:center;speak:none;font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;-webkit-backface-visibility:hidden;backface-visibility:hidden}i.icon:before{background:0 0!important}i.icon.loading{height:1em;line-height:1;-webkit-animation:icon-loading 2s linear infinite;animation:icon-loading 2s linear infinite}@-webkit-keyframes icon-loading{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes icon-loading{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}i.emphasized.icon,i.icon.active,i.icon.hover{opacity:1!important}i.disabled.icon{opacity:.45!important}i.fitted.icon{width:auto;margin:0}i.link.icon,i.link.icons{cursor:pointer;opacity:.8;-webkit-transition:opacity .1s ease;transition:opacity .1s ease}i.link.icon:hover,i.link.icons:hover{opacity:1!important}i.circular.icon{border-radius:500em!important;line-height:1!important;padding:.5em!important;box-shadow:0 0 0 .1em rgba(0,0,0,.1) inset;width:2em!important;height:2em!important}i.circular.inverted.icon{border:none;box-shadow:none}i.flipped.icon,i.horizontally.flipped.icon{-webkit-transform:scale(-1,1);transform:scale(-1,1)}i.vertically.flipped.icon{-webkit-transform:scale(1,-1);transform:scale(1,-1)}i.clockwise.rotated.icon,i.right.rotated.icon,i.rotated.icon{-webkit-transform:rotate(90deg);transform:rotate(90deg)}i.counterclockwise.rotated.icon,i.left.rotated.icon{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}i.bordered.icon{line-height:1;vertical-align:baseline;width:2em;height:2em;padding:.5em .41em!important;box-shadow:0 0 0 .1em rgba(0,0,0,.1) inset}i.bordered.inverted.icon{border:none;box-shadow:none}i.inverted.bordered.icon,i.inverted.circular.icon{background-color:#1B1C1D!important;color:#FFF!important}i.inverted.icon{color:#FFF}i.red.icon{color:#DB2828!important}i.inverted.red.icon{color:#FF695E!important}i.inverted.bordered.red.icon,i.inverted.circular.red.icon{background-color:#DB2828!important;color:#FFF!important}i.orange.icon{color:#F2711C!important}i.inverted.orange.icon{color:#FF851B!important}i.inverted.bordered.orange.icon,i.inverted.circular.orange.icon{background-color:#F2711C!important;color:#FFF!important}i.yellow.icon{color:#FBBD08!important}i.inverted.yellow.icon{color:#FFE21F!important}i.inverted.bordered.yellow.icon,i.inverted.circular.yellow.icon{background-color:#FBBD08!important;color:#FFF!important}i.olive.icon{color:#B5CC18!important}i.inverted.olive.icon{color:#D9E778!important}i.inverted.bordered.olive.icon,i.inverted.circular.olive.icon{background-color:#B5CC18!important;color:#FFF!important}i.green.icon{color:#21BA45!important}i.inverted.green.icon{color:#2ECC40!important}i.inverted.bordered.green.icon,i.inverted.circular.green.icon{background-color:#21BA45!important;color:#FFF!important}i.teal.icon{color:#00B5AD!important}i.inverted.teal.icon{color:#6DFFFF!important}i.inverted.bordered.teal.icon,i.inverted.circular.teal.icon{background-color:#00B5AD!important;color:#FFF!important}i.blue.icon{color:#2185D0!important}i.inverted.blue.icon{color:#54C8FF!important}i.inverted.bordered.blue.icon,i.inverted.circular.blue.icon{background-color:#2185D0!important;color:#FFF!important}i.violet.icon{color:#6435C9!important}i.inverted.violet.icon{color:#A291FB!important}i.inverted.bordered.violet.icon,i.inverted.circular.violet.icon{background-color:#6435C9!important;color:#FFF!important}i.purple.icon{color:#A333C8!important}i.inverted.purple.icon{color:#DC73FF!important}i.inverted.bordered.purple.icon,i.inverted.circular.purple.icon{background-color:#A333C8!important;color:#FFF!important}i.pink.icon{color:#E03997!important}i.inverted.pink.icon{color:#FF8EDF!important}i.inverted.bordered.pink.icon,i.inverted.circular.pink.icon{background-color:#E03997!important;color:#FFF!important}i.brown.icon{color:#A5673F!important}i.inverted.brown.icon{color:#D67C1C!important}i.inverted.bordered.brown.icon,i.inverted.circular.brown.icon{background-color:#A5673F!important;color:#FFF!important}i.grey.icon{color:#767676!important}i.inverted.grey.icon{color:#DCDDDE!important}i.inverted.bordered.grey.icon,i.inverted.circular.grey.icon{background-color:#767676!important;color:#FFF!important}i.black.icon{color:#1B1C1D!important}i.inverted.black.icon{color:#545454!important}i.inverted.bordered.black.icon,i.inverted.circular.black.icon{background-color:#1B1C1D!important;color:#FFF!important}i.mini.icon,i.mini.icons{line-height:1;font-size:.4em}i.tiny.icon,i.tiny.icons{line-height:1;font-size:.5em}i.small.icon,i.small.icons{line-height:1;font-size:.75em}i.icon,i.icons{font-size:1em}i.large.icon,i.large.icons{line-height:1;vertical-align:middle;font-size:1.5em}i.big.icon,i.big.icons{line-height:1;vertical-align:middle;font-size:2em}i.huge.icon,i.huge.icons{line-height:1;vertical-align:middle;font-size:4em}i.massive.icon,i.massive.icons{line-height:1;vertical-align:middle;font-size:8em}i.icons{display:inline-block;position:relative;line-height:1}i.icons .icon{position:absolute;top:50%;left:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%);margin:0}i.icons .icon:first-child{position:static;width:auto;height:auto;vertical-align:top;-webkit-transform:none;transform:none;margin-right:.25rem}i.icons .corner.icon{top:auto;left:auto;right:0;bottom:0;-webkit-transform:none;transform:none;font-size:.45em;text-shadow:-1px -1px 0 #FFF,1px -1px 0 #FFF,-1px 1px 0 #FFF,1px 1px 0 #FFF}i.icons .inverted.corner.icon{text-shadow:-1px -1px 0 #1B1C1D,1px -1px 0 #1B1C1D,-1px 1px 0 #1B1C1D,1px 1px 0 #1B1C1D}i.icon.search:before{content:"\f002"}i.icon.mail.outline:before{content:"\f003"}i.icon.signal:before{content:"\f012"}i.icon.setting:before{content:"\f013"}i.icon.home:before{content:"\f015"}i.icon.inbox:before{content:"\f01c"}i.icon.browser:before{content:"\f022"}i.icon.tag:before{content:"\f02b"}i.icon.tags:before{content:"\f02c"}i.icon.image:before{content:"\f03e"}i.icon.calendar:before{content:"\f073"}i.icon.comment:before{content:"\f075"}i.icon.shop:before{content:"\f07a"}i.icon.privacy:before{content:"\f084"}i.icon.settings:before{content:"\f085"}i.icon.comments:before{content:"\f086"}i.icon.external:before{content:"\f08e"}i.icon.trophy:before{content:"\f091"}i.icon.payment:before{content:"\f09d"}i.icon.feed:before{content:"\f09e"}i.icon.alarm.outline:before{content:"\f0a2"}i.icon.tasks:before{content:"\f0ae"}i.icon.cloud:before{content:"\f0c2"}i.icon.lab:before{content:"\f0c3"}i.icon.mail:before{content:"\f0e0"}i.icon.dashboard:before{content:"\f0e4"}i.icon.comment.outline:before{content:"\f0e5"}i.icon.comments.outline:before{content:"\f0e6"}i.icon.sitemap:before{content:"\f0e8"}i.icon.idea:before{content:"\f0eb"}i.icon.alarm:before{content:"\f0f3"}i.icon.terminal:before{content:"\f120"}i.icon.code:before{content:"\f121"}i.icon.protect:before{content:"\f132"}i.icon.calendar.outline:before{content:"\f133"}i.icon.ticket:before{content:"\f145"}i.icon.external.square:before{content:"\f14c"}i.icon.bug:before{content:"\f188"}i.icon.mail.square:before{content:"\f199"}i.icon.history:before{content:"\f1da"}i.icon.options:before{content:"\f1de"}i.icon.text.telephone:before{content:"\f1e4"}i.icon.find:before{content:"\f1e5"}i.icon.wifi:before{content:"\f1eb"}i.icon.alarm.mute:before{content:"\f1f6"}i.icon.alarm.mute.outline:before{content:"\f1f7"}i.icon.copyright:before{content:"\f1f9"}i.icon.at:before{content:"\f1fa"}i.icon.eyedropper:before{content:"\f1fb"}i.icon.paint.brush:before{content:"\f1fc"}i.icon.heartbeat:before{content:"\f21e"}i.icon.mouse.pointer:before{content:"\f245"}i.icon.hourglass.empty:before{content:"\f250"}i.icon.hourglass.start:before{content:"\f251"}i.icon.hourglass.half:before{content:"\f252"}i.icon.hourglass.end:before{content:"\f253"}i.icon.hourglass.full:before{content:"\f254"}i.icon.hand.pointer:before{content:"\f25a"}i.icon.trademark:before{content:"\f25c"}i.icon.registered:before{content:"\f25d"}i.icon.creative.commons:before{content:"\f25e"}i.icon.add.to.calendar:before{content:"\f271"}i.icon.remove.from.calendar:before{content:"\f272"}i.icon.delete.calendar:before{content:"\f273"}i.icon.checked.calendar:before{content:"\f274"}i.icon.industry:before{content:"\f275"}i.icon.shopping.bag:before{content:"\f290"}i.icon.shopping.basket:before{content:"\f291"}i.icon.hashtag:before{content:"\f292"}i.icon.percent:before{content:"\f295"}i.icon.wait:before{content:"\f017"}i.icon.download:before{content:"\f019"}i.icon.repeat:before{content:"\f01e"}i.icon.refresh:before{content:"\f021"}i.icon.lock:before{content:"\f023"}i.icon.bookmark:before{content:"\f02e"}i.icon.print:before{content:"\f02f"}i.icon.write:before{content:"\f040"}i.icon.adjust:before{content:"\f042"}i.icon.theme:before{content:"\f043"}i.icon.edit:before{content:"\f044"}i.icon.external.share:before{content:"\f045"}i.icon.ban:before{content:"\f05e"}i.icon.mail.forward:before,i.icon.share:before{content:"\f064"}i.icon.expand:before{content:"\f065"}i.icon.compress:before{content:"\f066"}i.icon.unhide:before{content:"\f06e"}i.icon.hide:before{content:"\f070"}i.icon.random:before{content:"\f074"}i.icon.retweet:before{content:"\f079"}i.icon.sign.out:before{content:"\f08b"}i.icon.pin:before{content:"\f08d"}i.icon.sign.in:before{content:"\f090"}i.icon.upload:before{content:"\f093"}i.icon.call:before{content:"\f095"}i.icon.remove.bookmark:before{content:"\f097"}i.icon.call.square:before{content:"\f098"}i.icon.unlock:before{content:"\f09c"}i.icon.configure:before{content:"\f0ad"}i.icon.filter:before{content:"\f0b0"}i.icon.wizard:before{content:"\f0d0"}i.icon.undo:before{content:"\f0e2"}i.icon.exchange:before{content:"\f0ec"}i.icon.cloud.download:before{content:"\f0ed"}i.icon.cloud.upload:before{content:"\f0ee"}i.icon.reply:before{content:"\f112"}i.icon.reply.all:before{content:"\f122"}i.icon.erase:before{content:"\f12d"}i.icon.unlock.alternate:before{content:"\f13e"}i.icon.write.square:before{content:"\f14b"}i.icon.share.square:before{content:"\f14d"}i.icon.archive:before{content:"\f187"}i.icon.translate:before{content:"\f1ab"}i.icon.recycle:before{content:"\f1b8"}i.icon.send:before{content:"\f1d8"}i.icon.send.outline:before{content:"\f1d9"}i.icon.share.alternate:before{content:"\f1e0"}i.icon.share.alternate.square:before{content:"\f1e1"}i.icon.add.to.cart:before{content:"\f217"}i.icon.in.cart:before{content:"\f218"}i.icon.add.user:before{content:"\f234"}i.icon.remove.user:before{content:"\f235"}i.icon.object.group:before{content:"\f247"}i.icon.object.ungroup:before{content:"\f248"}i.icon.clone:before{content:"\f24d"}i.icon.talk:before{content:"\f27a"}i.icon.talk.outline:before{content:"\f27b"}i.icon.help.circle:before{content:"\f059"}i.icon.info.circle:before{content:"\f05a"}i.icon.warning.circle:before{content:"\f06a"}i.icon.warning.sign:before{content:"\f071"}i.icon.announcement:before{content:"\f0a1"}i.icon.help:before{content:"\f128"}i.icon.info:before{content:"\f129"}i.icon.warning:before{content:"\f12a"}i.icon.birthday:before{content:"\f1fd"}i.icon.help.circle.outline:before{content:"\f29c"}i.icon.user:before{content:"\f007"}i.icon.users:before{content:"\f0c0"}i.icon.doctor:before{content:"\f0f0"}i.icon.handicap:before{content:"\f193"}i.icon.student:before{content:"\f19d"}i.icon.child:before{content:"\f1ae"}i.icon.spy:before{content:"\f21b"}i.icon.female:before{content:"\f182"}i.icon.male:before{content:"\f183"}i.icon.woman:before{content:"\f221"}i.icon.man:before{content:"\f222"}i.icon.non.binary.transgender:before{content:"\f223"}i.icon.intergender:before{content:"\f224"}i.icon.transgender:before{content:"\f225"}i.icon.lesbian:before{content:"\f226"}i.icon.gay:before{content:"\f227"}i.icon.heterosexual:before{content:"\f228"}i.icon.other.gender:before{content:"\f229"}i.icon.other.gender.vertical:before{content:"\f22a"}i.icon.other.gender.horizontal:before{content:"\f22b"}i.icon.neuter:before{content:"\f22c"}i.icon.genderless:before{content:"\f22d"}i.icon.universal.access:before{content:"\f29a"}i.icon.wheelchair:before{content:"\f29b"}i.icon.blind:before{content:"\f29d"}i.icon.audio.description:before{content:"\f29e"}i.icon.volume.control.phone:before{content:"\f2a0"}i.icon.braille:before{content:"\f2a1"}i.icon.asl:before{content:"\f2a3"}i.icon.assistive.listening.systems:before{content:"\f2a2"}i.icon.deafness:before{content:"\f2a4"}i.icon.sign.language:before{content:"\f2a7"}i.icon.low.vision:before{content:"\f2a8"}i.icon.block.layout:before{content:"\f009"}i.icon.grid.layout:before{content:"\f00a"}i.icon.list.layout:before{content:"\f00b"}i.icon.zoom:before{content:"\f00e"}i.icon.zoom.out:before{content:"\f010"}i.icon.resize.vertical:before{content:"\f07d"}i.icon.resize.horizontal:before{content:"\f07e"}i.icon.maximize:before{content:"\f0b2"}i.icon.crop:before{content:"\f125"}i.icon.cocktail:before{content:"\f000"}i.icon.road:before{content:"\f018"}i.icon.flag:before{content:"\f024"}i.icon.book:before{content:"\f02d"}i.icon.gift:before{content:"\f06b"}i.icon.leaf:before{content:"\f06c"}i.icon.fire:before{content:"\f06d"}i.icon.plane:before{content:"\f072"}i.icon.magnet:before{content:"\f076"}i.icon.lemon:before{content:"\f094"}i.icon.world:before{content:"\f0ac"}i.icon.travel:before{content:"\f0b1"}i.icon.shipping:before{content:"\f0d1"}i.icon.money:before{content:"\f0d6"}i.icon.legal:before{content:"\f0e3"}i.icon.lightning:before{content:"\f0e7"}i.icon.umbrella:before{content:"\f0e9"}i.icon.treatment:before{content:"\f0f1"}i.icon.suitcase:before{content:"\f0f2"}i.icon.bar:before{content:"\f0fc"}i.icon.flag.outline:before{content:"\f11d"}i.icon.flag.checkered:before{content:"\f11e"}i.icon.puzzle:before{content:"\f12e"}i.icon.fire.extinguisher:before{content:"\f134"}i.icon.rocket:before{content:"\f135"}i.icon.anchor:before{content:"\f13d"}i.icon.bullseye:before{content:"\f140"}i.icon.sun:before{content:"\f185"}i.icon.moon:before{content:"\f186"}i.icon.fax:before{content:"\f1ac"}i.icon.life.ring:before{content:"\f1cd"}i.icon.bomb:before{content:"\f1e2"}i.icon.soccer:before{content:"\f1e3"}i.icon.calculator:before{content:"\f1ec"}i.icon.diamond:before{content:"\f219"}i.icon.sticky.note:before{content:"\f249"}i.icon.sticky.note.outline:before{content:"\f24a"}i.icon.law:before{content:"\f24e"}i.icon.hand.peace:before{content:"\f25b"}i.icon.hand.rock:before{content:"\f255"}i.icon.hand.paper:before{content:"\f256"}i.icon.hand.scissors:before{content:"\f257"}i.icon.hand.lizard:before{content:"\f258"}i.icon.hand.spock:before{content:"\f259"}i.icon.tv:before{content:"\f26c"}i.icon.crosshairs:before{content:"\f05b"}i.icon.asterisk:before{content:"\f069"}i.icon.square.outline:before{content:"\f096"}i.icon.certificate:before{content:"\f0a3"}i.icon.square:before{content:"\f0c8"}i.icon.quote.left:before{content:"\f10d"}i.icon.quote.right:before{content:"\f10e"}i.icon.spinner:before{content:"\f110"}i.icon.circle:before{content:"\f111"}i.icon.ellipsis.horizontal:before{content:"\f141"}i.icon.ellipsis.vertical:before{content:"\f142"}i.icon.cube:before{content:"\f1b2"}i.icon.cubes:before{content:"\f1b3"}i.icon.circle.notched:before{content:"\f1ce"}i.icon.circle.thin:before{content:"\f1db"}i.icon.checkmark:before{content:"\f00c"}i.icon.remove:before{content:"\f00d"}i.icon.checkmark.box:before{content:"\f046"}i.icon.move:before{content:"\f047"}i.icon.add.circle:before{content:"\f055"}i.icon.minus.circle:before{content:"\f056"}i.icon.remove.circle:before{content:"\f057"}i.icon.check.circle:before{content:"\f058"}i.icon.remove.circle.outline:before{content:"\f05c"}i.icon.check.circle.outline:before{content:"\f05d"}i.icon.plus:before{content:"\f067"}i.icon.minus:before{content:"\f068"}i.icon.add.square:before{content:"\f0fe"}i.icon.radio:before{content:"\f10c"}i.icon.minus.square:before{content:"\f146"}i.icon.minus.square.outline:before{content:"\f147"}i.icon.check.square:before{content:"\f14a"}i.icon.selected.radio:before{content:"\f192"}i.icon.plus.square.outline:before{content:"\f196"}i.icon.toggle.off:before{content:"\f204"}i.icon.toggle.on:before{content:"\f205"}i.icon.film:before{content:"\f008"}i.icon.sound:before{content:"\f025"}i.icon.photo:before{content:"\f030"}i.icon.bar.chart:before{content:"\f080"}i.icon.camera.retro:before{content:"\f083"}i.icon.newspaper:before{content:"\f1ea"}i.icon.area.chart:before{content:"\f1fe"}i.icon.pie.chart:before{content:"\f200"}i.icon.line.chart:before{content:"\f201"}i.icon.arrow.circle.outline.down:before{content:"\f01a"}i.icon.arrow.circle.outline.up:before{content:"\f01b"}i.icon.chevron.left:before{content:"\f053"}i.icon.chevron.right:before{content:"\f054"}i.icon.arrow.left:before{content:"\f060"}i.icon.arrow.right:before{content:"\f061"}i.icon.arrow.up:before{content:"\f062"}i.icon.arrow.down:before{content:"\f063"}i.icon.chevron.up:before{content:"\f077"}i.icon.chevron.down:before{content:"\f078"}i.icon.pointing.right:before{content:"\f0a4"}i.icon.pointing.left:before{content:"\f0a5"}i.icon.pointing.up:before{content:"\f0a6"}i.icon.pointing.down:before{content:"\f0a7"}i.icon.arrow.circle.left:before{content:"\f0a8"}i.icon.arrow.circle.right:before{content:"\f0a9"}i.icon.arrow.circle.up:before{content:"\f0aa"}i.icon.arrow.circle.down:before{content:"\f0ab"}i.icon.caret.down:before{content:"\f0d7"}i.icon.caret.up:before{content:"\f0d8"}i.icon.caret.left:before{content:"\f0d9"}i.icon.caret.right:before{content:"\f0da"}i.icon.angle.double.left:before{content:"\f100"}i.icon.angle.double.right:before{content:"\f101"}i.icon.angle.double.up:before{content:"\f102"}i.icon.angle.double.down:before{content:"\f103"}i.icon.angle.left:before{content:"\f104"}i.icon.angle.right:before{content:"\f105"}i.icon.angle.up:before{content:"\f106"}i.icon.angle.down:before{content:"\f107"}i.icon.chevron.circle.left:before{content:"\f137"}i.icon.chevron.circle.right:before{content:"\f138"}i.icon.chevron.circle.up:before{content:"\f139"}i.icon.chevron.circle.down:before{content:"\f13a"}i.icon.toggle.down:before{content:"\f150"}i.icon.toggle.up:before{content:"\f151"}i.icon.toggle.right:before{content:"\f152"}i.icon.long.arrow.down:before{content:"\f175"}i.icon.long.arrow.up:before{content:"\f176"}i.icon.long.arrow.left:before{content:"\f177"}i.icon.long.arrow.right:before{content:"\f178"}i.icon.arrow.circle.outline.right:before{content:"\f18e"}i.icon.arrow.circle.outline.left:before{content:"\f190"}i.icon.toggle.left:before{content:"\f191"}i.icon.tablet:before{content:"\f10a"}i.icon.mobile:before{content:"\f10b"}i.icon.battery.full:before{content:"\f240"}i.icon.battery.high:before{content:"\f241"}i.icon.battery.medium:before{content:"\f242"}i.icon.battery.low:before{content:"\f243"}i.icon.battery.empty:before{content:"\f244"}i.icon.power:before{content:"\f011"}i.icon.trash.outline:before{content:"\f014"}i.icon.disk.outline:before{content:"\f0a0"}i.icon.desktop:before{content:"\f108"}i.icon.laptop:before{content:"\f109"}i.icon.game:before{content:"\f11b"}i.icon.keyboard:before{content:"\f11c"}i.icon.plug:before{content:"\f1e6"}i.icon.trash:before{content:"\f1f8"}i.icon.file.outline:before{content:"\f016"}i.icon.folder:before{content:"\f07b"}i.icon.folder.open:before{content:"\f07c"}i.icon.file.text.outline:before{content:"\f0f6"}i.icon.folder.outline:before{content:"\f114"}i.icon.folder.open.outline:before{content:"\f115"}i.icon.level.up:before{content:"\f148"}i.icon.level.down:before{content:"\f149"}i.icon.file:before{content:"\f15b"}i.icon.file.text:before{content:"\f15c"}i.icon.file.pdf.outline:before{content:"\f1c1"}i.icon.file.word.outline:before{content:"\f1c2"}i.icon.file.excel.outline:before{content:"\f1c3"}i.icon.file.powerpoint.outline:before{content:"\f1c4"}i.icon.file.image.outline:before{content:"\f1c5"}i.icon.file.archive.outline:before{content:"\f1c6"}i.icon.file.audio.outline:before{content:"\f1c7"}i.icon.file.video.outline:before{content:"\f1c8"}i.icon.file.code.outline:before{content:"\f1c9"}i.icon.qrcode:before{content:"\f029"}i.icon.barcode:before{content:"\f02a"}i.icon.rss:before{content:"\f09e"}i.icon.fork:before{content:"\f126"}i.icon.html5:before{content:"\f13b"}i.icon.css3:before{content:"\f13c"}i.icon.rss.square:before{content:"\f143"}i.icon.openid:before{content:"\f19b"}i.icon.database:before{content:"\f1c0"}i.icon.server:before{content:"\f233"}i.icon.usb:before{content:"\f287"}i.icon.bluetooth:before{content:"\f293"}i.icon.bluetooth.alternative:before{content:"\f294"}i.icon.heart:before{content:"\f004"}i.icon.star:before{content:"\f005"}i.icon.empty.star:before{content:"\f006"}i.icon.thumbs.outline.up:before{content:"\f087"}i.icon.thumbs.outline.down:before{content:"\f088"}i.icon.star.half:before{content:"\f089"}i.icon.empty.heart:before{content:"\f08a"}i.icon.smile:before{content:"\f118"}i.icon.frown:before{content:"\f119"}i.icon.meh:before{content:"\f11a"}i.icon.star.half.empty:before{content:"\f123"}i.icon.thumbs.up:before{content:"\f164"}i.icon.thumbs.down:before{content:"\f165"}i.icon.music:before{content:"\f001"}i.icon.video.play.outline:before{content:"\f01d"}i.icon.volume.off:before{content:"\f026"}i.icon.volume.down:before{content:"\f027"}i.icon.volume.up:before{content:"\f028"}i.icon.record:before{content:"\f03d"}i.icon.step.backward:before{content:"\f048"}i.icon.fast.backward:before{content:"\f049"}i.icon.backward:before{content:"\f04a"}i.icon.play:before{content:"\f04b"}i.icon.pause:before{content:"\f04c"}i.icon.stop:before{content:"\f04d"}i.icon.forward:before{content:"\f04e"}i.icon.fast.forward:before{content:"\f050"}i.icon.step.forward:before{content:"\f051"}i.icon.eject:before{content:"\f052"}i.icon.unmute:before{content:"\f130"}i.icon.mute:before{content:"\f131"}i.icon.video.play:before{content:"\f144"}i.icon.closed.captioning:before{content:"\f20a"}i.icon.pause.circle:before{content:"\f28b"}i.icon.pause.circle.outline:before{content:"\f28c"}i.icon.stop.circle:before{content:"\f28d"}i.icon.stop.circle.outline:before{content:"\f28e"}i.icon.marker:before{content:"\f041"}i.icon.coffee:before{content:"\f0f4"}i.icon.food:before{content:"\f0f5"}i.icon.building.outline:before{content:"\f0f7"}i.icon.hospital:before{content:"\f0f8"}i.icon.emergency:before{content:"\f0f9"}i.icon.first.aid:before{content:"\f0fa"}i.icon.military:before{content:"\f0fb"}i.icon.h:before{content:"\f0fd"}i.icon.location.arrow:before{content:"\f124"}i.icon.compass:before{content:"\f14e"}i.icon.space.shuttle:before{content:"\f197"}i.icon.university:before{content:"\f19c"}i.icon.building:before{content:"\f1ad"}i.icon.paw:before{content:"\f1b0"}i.icon.spoon:before{content:"\f1b1"}i.icon.car:before{content:"\f1b9"}i.icon.taxi:before{content:"\f1ba"}i.icon.tree:before{content:"\f1bb"}i.icon.bicycle:before{content:"\f206"}i.icon.bus:before{content:"\f207"}i.icon.ship:before{content:"\f21a"}i.icon.motorcycle:before{content:"\f21c"}i.icon.street.view:before{content:"\f21d"}i.icon.hotel:before{content:"\f236"}i.icon.train:before{content:"\f238"}i.icon.subway:before{content:"\f239"}i.icon.map.pin:before{content:"\f276"}i.icon.map.signs:before{content:"\f277"}i.icon.map.outline:before{content:"\f278"}i.icon.map:before{content:"\f279"}i.icon.table:before{content:"\f0ce"}i.icon.columns:before{content:"\f0db"}i.icon.sort:before{content:"\f0dc"}i.icon.sort.descending:before{content:"\f0dd"}i.icon.sort.ascending:before{content:"\f0de"}i.icon.sort.alphabet.ascending:before{content:"\f15d"}i.icon.sort.alphabet.descending:before{content:"\f15e"}i.icon.sort.content.ascending:before{content:"\f160"}i.icon.sort.content.descending:before{content:"\f161"}i.icon.sort.numeric.ascending:before{content:"\f162"}i.icon.sort.numeric.descending:before{content:"\f163"}i.icon.font:before{content:"\f031"}i.icon.bold:before{content:"\f032"}i.icon.italic:before{content:"\f033"}i.icon.text.height:before{content:"\f034"}i.icon.text.width:before{content:"\f035"}i.icon.align.left:before{content:"\f036"}i.icon.align.center:before{content:"\f037"}i.icon.align.right:before{content:"\f038"}i.icon.align.justify:before{content:"\f039"}i.icon.list:before{content:"\f03a"}i.icon.outdent:before{content:"\f03b"}i.icon.indent:before{content:"\f03c"}i.icon.cut:before{content:"\f0c4"}i.icon.copy:before{content:"\f0c5"}i.icon.attach:before{content:"\f0c6"}i.icon.save:before{content:"\f0c7"}i.icon.content:before{content:"\f0c9"}i.icon.unordered.list:before{content:"\f0ca"}i.icon.ordered.list:before{content:"\f0cb"}i.icon.strikethrough:before{content:"\f0cc"}i.icon.underline:before{content:"\f0cd"}i.icon.paste:before{content:"\f0ea"}i.icon.unlinkify:before{content:"\f127"}i.icon.superscript:before{content:"\f12b"}i.icon.subscript:before{content:"\f12c"}i.icon.header:before{content:"\f1dc"}i.icon.paragraph:before{content:"\f1dd"}i.icon.text.cursor:before{content:"\f246"}i.icon.euro:before{content:"\f153"}i.icon.pound:before{content:"\f154"}i.icon.dollar:before{content:"\f155"}i.icon.rupee:before{content:"\f156"}i.icon.yen:before{content:"\f157"}i.icon.ruble:before{content:"\f158"}i.icon.won:before{content:"\f159"}i.icon.bitcoin:before{content:"\f15a"}i.icon.lira:before{content:"\f195"}i.icon.shekel:before{content:"\f20b"}i.icon.paypal:before{content:"\f1ed"}i.icon.google.wallet:before{content:"\f1ee"}i.icon.visa:before{content:"\f1f0"}i.icon.mastercard:before{content:"\f1f1"}i.icon.discover:before{content:"\f1f2"}i.icon.american.express:before{content:"\f1f3"}i.icon.paypal.card:before{content:"\f1f4"}i.icon.stripe:before{content:"\f1f5"}i.icon.japan.credit.bureau:before{content:"\f24b"}i.icon.diners.club:before{content:"\f24c"}i.icon.credit.card.alternative:before{content:"\f283"}i.icon.twitter.square:before{content:"\f081"}i.icon.facebook.square:before{content:"\f082"}i.icon.linkedin.square:before{content:"\f08c"}i.icon.github.square:before{content:"\f092"}i.icon.twitter:before{content:"\f099"}i.icon.facebook.f:before{content:"\f09a"}i.icon.github:before{content:"\f09b"}i.icon.pinterest.square:before{content:"\f0d3"}i.icon.google.plus.square:before{content:"\f0d4"}i.icon.google.plus:before{content:"\f0d5"}i.icon.linkedin:before{content:"\f0e1"}i.icon.github.alternate:before{content:"\f113"}i.icon.maxcdn:before{content:"\f136"}i.icon.youtube.square:before{content:"\f166"}i.icon.youtube:before{content:"\f167"}i.icon.xing:before{content:"\f168"}i.icon.xing.square:before{content:"\f169"}i.icon.youtube.play:before{content:"\f16a"}i.icon.dropbox:before{content:"\f16b"}i.icon.stack.overflow:before{content:"\f16c"}i.icon.instagram:before{content:"\f16d"}i.icon.flickr:before{content:"\f16e"}i.icon.adn:before{content:"\f170"}i.icon.bitbucket:before{content:"\f171"}i.icon.bitbucket.square:before{content:"\f172"}i.icon.tumblr:before{content:"\f173"}i.icon.tumblr.square:before{content:"\f174"}i.icon.apple:before{content:"\f179"}i.icon.windows:before{content:"\f17a"}i.icon.android:before{content:"\f17b"}i.icon.linux:before{content:"\f17c"}i.icon.dribble:before{content:"\f17d"}i.icon.skype:before{content:"\f17e"}i.icon.foursquare:before{content:"\f180"}i.icon.trello:before{content:"\f181"}i.icon.gittip:before{content:"\f184"}i.icon.vk:before{content:"\f189"}i.icon.weibo:before{content:"\f18a"}i.icon.renren:before{content:"\f18b"}i.icon.pagelines:before{content:"\f18c"}i.icon.stack.exchange:before{content:"\f18d"}i.icon.vimeo.square:before{content:"\f194"}i.icon.slack:before{content:"\f198"}i.icon.wordpress:before{content:"\f19a"}i.icon.yahoo:before{content:"\f19e"}i.icon.google:before{content:"\f1a0"}i.icon.reddit:before{content:"\f1a1"}i.icon.reddit.square:before{content:"\f1a2"}i.icon.stumbleupon.circle:before{content:"\f1a3"}i.icon.stumbleupon:before{content:"\f1a4"}i.icon.delicious:before{content:"\f1a5"}i.icon.digg:before{content:"\f1a6"}i.icon.pied.piper:before{content:"\f1a7"}i.icon.pied.piper.alternate:before{content:"\f1a8"}i.icon.drupal:before{content:"\f1a9"}i.icon.joomla:before{content:"\f1aa"}i.icon.behance:before{content:"\f1b4"}i.icon.behance.square:before{content:"\f1b5"}i.icon.steam:before{content:"\f1b6"}i.icon.steam.square:before{content:"\f1b7"}i.icon.spotify:before{content:"\f1bc"}i.icon.deviantart:before{content:"\f1bd"}i.icon.soundcloud:before{content:"\f1be"}i.icon.vine:before{content:"\f1ca"}i.icon.codepen:before{content:"\f1cb"}i.icon.jsfiddle:before{content:"\f1cc"}i.icon.rebel:before{content:"\f1d0"}i.icon.empire:before{content:"\f1d1"}i.icon.git.square:before{content:"\f1d2"}i.icon.git:before{content:"\f1d3"}i.icon.hacker.news:before{content:"\f1d4"}i.icon.tencent.weibo:before{content:"\f1d5"}i.icon.qq:before{content:"\f1d6"}i.icon.wechat:before{content:"\f1d7"}i.icon.slideshare:before{content:"\f1e7"}i.icon.twitch:before{content:"\f1e8"}i.icon.yelp:before{content:"\f1e9"}i.icon.lastfm:before{content:"\f202"}i.icon.lastfm.square:before{content:"\f203"}i.icon.ioxhost:before{content:"\f208"}i.icon.angellist:before{content:"\f209"}i.icon.meanpath:before{content:"\f20c"}i.icon.buysellads:before{content:"\f20d"}i.icon.connectdevelop:before{content:"\f20e"}i.icon.dashcube:before{content:"\f210"}i.icon.forumbee:before{content:"\f211"}i.icon.leanpub:before{content:"\f212"}i.icon.sellsy:before{content:"\f213"}i.icon.shirtsinbulk:before{content:"\f214"}i.icon.simplybuilt:before{content:"\f215"}i.icon.skyatlas:before{content:"\f216"}i.icon.facebook:before{content:"\f230"}i.icon.pinterest:before{content:"\f231"}i.icon.whatsapp:before{content:"\f232"}i.icon.viacoin:before{content:"\f237"}i.icon.medium:before{content:"\f23a"}i.icon.y.combinator:before{content:"\f23b"}i.icon.optinmonster:before{content:"\f23c"}i.icon.opencart:before{content:"\f23d"}i.icon.expeditedssl:before{content:"\f23e"}i.icon.gg:before{content:"\f260"}i.icon.gg.circle:before{content:"\f261"}i.icon.tripadvisor:before{content:"\f262"}i.icon.odnoklassniki:before{content:"\f263"}i.icon.odnoklassniki.square:before{content:"\f264"}i.icon.pocket:before{content:"\f265"}i.icon.wikipedia:before{content:"\f266"}i.icon.safari:before{content:"\f267"}i.icon.chrome:before{content:"\f268"}i.icon.firefox:before{content:"\f269"}i.icon.opera:before{content:"\f26a"}i.icon.internet.explorer:before{content:"\f26b"}i.icon.contao:before{content:"\f26d"}i.icon.\35 00px:before{content:"\f26e"}i.icon.amazon:before{content:"\f270"}i.icon.houzz:before{content:"\f27c"}i.icon.vimeo:before{content:"\f27d"}i.icon.black.tie:before{content:"\f27e"}i.icon.fonticons:before{content:"\f280"}i.icon.reddit.alien:before{content:"\f281"}i.icon.microsoft.edge:before{content:"\f282"}i.icon.codiepie:before{content:"\f284"}i.icon.modx:before{content:"\f285"}i.icon.fort.awesome:before{content:"\f286"}i.icon.product.hunt:before{content:"\f288"}i.icon.mixcloud:before{content:"\f289"}i.icon.scribd:before{content:"\f28a"}i.icon.gitlab:before{content:"\f296"}i.icon.wpbeginner:before{content:"\f297"}i.icon.wpforms:before{content:"\f298"}i.icon.envira.gallery:before{content:"\f299"}i.icon.glide:before{content:"\f2a5"}i.icon.glide.g:before{content:"\f2a6"}i.icon.viadeo:before{content:"\f2a9"}i.icon.viadeo.square:before{content:"\f2aa"}i.icon.snapchat:before{content:"\f2ab"}i.icon.snapchat.ghost:before{content:"\f2ac"}i.icon.snapchat.square:before{content:"\f2ad"}i.icon.pied.piper.hat:before{content:"\f2ae"}i.icon.first.order:before{content:"\f2b0"}i.icon.yoast:before{content:"\f2b1"}i.icon.themeisle:before{content:"\f2b2"}i.icon.google.plus.circle:before{content:"\f2b3"}i.icon.font.awesome:before{content:"\f2b4"}i.icon.like:before{content:"\f004"}i.icon.favorite:before{content:"\f005"}i.icon.video:before{content:"\f008"}i.icon.check:before{content:"\f00c"}i.icon.cancel:before,i.icon.close:before,i.icon.delete:before,i.icon.x:before{content:"\f00d"}i.icon.magnify:before,i.icon.zoom.in:before{content:"\f00e"}i.icon.shutdown:before{content:"\f011"}i.icon.clock:before,i.icon.time:before{content:"\f017"}i.icon.play.circle.outline:before{content:"\f01d"}i.icon.headphone:before{content:"\f025"}i.icon.camera:before{content:"\f030"}i.icon.video.camera:before{content:"\f03d"}i.icon.picture:before{content:"\f03e"}i.icon.compose:before,i.icon.pencil:before{content:"\f040"}i.icon.point:before{content:"\f041"}i.icon.tint:before{content:"\f043"}i.icon.signup:before{content:"\f044"}i.icon.plus.circle:before{content:"\f055"}i.icon.question.circle:before{content:"\f059"}i.icon.dont:before{content:"\f05e"}i.icon.minimize:before{content:"\f066"}i.icon.add:before{content:"\f067"}i.icon.attention:before,i.icon.exclamation.circle:before{content:"\f06a"}i.icon.eye:before{content:"\f06e"}i.icon.exclamation.triangle:before{content:"\f071"}i.icon.shuffle:before{content:"\f074"}i.icon.chat:before{content:"\f075"}i.icon.cart:before,i.icon.shopping.cart:before{content:"\f07a"}i.icon.bar.graph:before{content:"\f080"}i.icon.key:before{content:"\f084"}i.icon.cogs:before{content:"\f085"}i.icon.discussions:before{content:"\f086"}i.icon.like.outline:before{content:"\f087"}i.icon.dislike.outline:before{content:"\f088"}i.icon.heart.outline:before{content:"\f08a"}i.icon.log.out:before{content:"\f08b"}i.icon.thumb.tack:before{content:"\f08d"}i.icon.winner:before{content:"\f091"}i.icon.phone:before{content:"\f095"}i.icon.bookmark.outline:before{content:"\f097"}i.icon.phone.square:before{content:"\f098"}i.icon.credit.card:before{content:"\f09d"}i.icon.hdd.outline:before{content:"\f0a0"}i.icon.bullhorn:before{content:"\f0a1"}i.icon.bell.outline:before{content:"\f0a2"}i.icon.hand.outline.right:before{content:"\f0a4"}i.icon.hand.outline.left:before{content:"\f0a5"}i.icon.hand.outline.up:before{content:"\f0a6"}i.icon.hand.outline.down:before{content:"\f0a7"}i.icon.globe:before{content:"\f0ac"}i.icon.wrench:before{content:"\f0ad"}i.icon.briefcase:before{content:"\f0b1"}i.icon.group:before{content:"\f0c0"}i.icon.chain:before,i.icon.linkify:before{content:"\f0c1"}i.icon.flask:before{content:"\f0c3"}i.icon.bars:before,i.icon.sidebar:before{content:"\f0c9"}i.icon.list.ul:before{content:"\f0ca"}i.icon.list.ol:before,i.icon.numbered.list:before{content:"\f0cb"}i.icon.magic:before{content:"\f0d0"}i.icon.truck:before{content:"\f0d1"}i.icon.currency:before{content:"\f0d6"}i.icon.dropdown:before,i.icon.triangle.down:before{content:"\f0d7"}i.icon.triangle.up:before{content:"\f0d8"}i.icon.triangle.left:before{content:"\f0d9"}i.icon.triangle.right:before{content:"\f0da"}i.icon.envelope:before{content:"\f0e0"}i.icon.conversation:before{content:"\f0e6"}i.icon.rain:before{content:"\f0e9"}i.icon.clipboard:before{content:"\f0ea"}i.icon.lightbulb:before{content:"\f0eb"}i.icon.bell:before{content:"\f0f3"}i.icon.ambulance:before{content:"\f0f9"}i.icon.medkit:before{content:"\f0fa"}i.icon.fighter.jet:before{content:"\f0fb"}i.icon.beer:before{content:"\f0fc"}i.icon.plus.square:before{content:"\f0fe"}i.icon.computer:before{content:"\f108"}i.icon.gamepad:before{content:"\f11b"}i.icon.star.half.full:before{content:"\f123"}i.icon.broken.chain:before{content:"\f127"}i.icon.question:before{content:"\f128"}i.icon.exclamation:before{content:"\f12a"}i.icon.eraser:before{content:"\f12d"}i.icon.microphone:before{content:"\f130"}i.icon.microphone.slash:before{content:"\f131"}i.icon.shield:before{content:"\f132"}i.icon.target:before{content:"\f140"}i.icon.play.circle:before{content:"\f144"}i.icon.pencil.square:before{content:"\f14b"}i.icon.eur:before{content:"\f153"}i.icon.gbp:before{content:"\f154"}i.icon.usd:before{content:"\f155"}i.icon.inr:before{content:"\f156"}i.icon.cny:before,i.icon.jpy:before,i.icon.rmb:before{content:"\f157"}i.icon.rouble:before,i.icon.rub:before{content:"\f158"}i.icon.krw:before{content:"\f159"}i.icon.btc:before{content:"\f15a"}i.icon.gratipay:before{content:"\f184"}i.icon.zip:before{content:"\f187"}i.icon.dot.circle.outline:before{content:"\f192"}i.icon.try:before{content:"\f195"}i.icon.graduation:before{content:"\f19d"}i.icon.circle.outline:before{content:"\f1db"}i.icon.sliders:before{content:"\f1de"}i.icon.weixin:before{content:"\f1d7"}i.icon.teletype:before,i.icon.tty:before{content:"\f1e4"}i.icon.binoculars:before{content:"\f1e5"}i.icon.power.cord:before{content:"\f1e6"}i.icon.wi-fi:before{content:"\f1eb"}i.icon.visa.card:before{content:"\f1f0"}i.icon.mastercard.card:before{content:"\f1f1"}i.icon.discover.card:before{content:"\f1f2"}i.icon.american.express.card:before,i.icon.amex:before{content:"\f1f3"}i.icon.stripe.card:before{content:"\f1f5"}i.icon.bell.slash:before{content:"\f1f6"}i.icon.bell.slash.outline:before{content:"\f1f7"}i.icon.area.graph:before{content:"\f1fe"}i.icon.pie.graph:before{content:"\f200"}i.icon.line.graph:before{content:"\f201"}i.icon.cc:before{content:"\f20a"}i.icon.ils:before,i.icon.sheqel:before{content:"\f20b"}i.icon.plus.cart:before{content:"\f217"}i.icon.arrow.down.cart:before{content:"\f218"}i.icon.detective:before{content:"\f21b"}i.icon.venus:before{content:"\f221"}i.icon.mars:before{content:"\f222"}i.icon.mercury:before{content:"\f223"}i.icon.intersex:before{content:"\f224"}i.icon.female.homosexual:before,i.icon.venus.double:before{content:"\f226"}i.icon.male.homosexual:before,i.icon.mars.double:before{content:"\f227"}i.icon.venus.mars:before{content:"\f228"}i.icon.mars.alternate:before,i.icon.mars.stroke:before{content:"\f229"}i.icon.mars.stroke.vertical:before,i.icon.mars.vertical:before{content:"\f22a"}i.icon.mars.horizontal:before,i.icon.mars.stroke.horizontal:before{content:"\f22b"}i.icon.asexual:before{content:"\f22d"}i.icon.facebook.official:before{content:"\f230"}i.icon.user.plus:before{content:"\f234"}i.icon.user.cancel:before,i.icon.user.close:before,i.icon.user.delete:before,i.icon.user.times:before,i.icon.user.x:before{content:"\f235"}i.icon.bed:before{content:"\f236"}i.icon.yc:before,i.icon.ycombinator:before{content:"\f23b"}i.icon.battery.four:before{content:"\f240"}i.icon.battery.three.quarters:before,i.icon.battery.three:before{content:"\f241"}i.icon.battery.half:before,i.icon.battery.two:before{content:"\f242"}i.icon.battery.one:before,i.icon.battery.quarter:before{content:"\f243"}i.icon.battery.zero:before{content:"\f244"}i.icon.i.cursor:before{content:"\f246"}i.icon.japan.credit.bureau.card:before,i.icon.jcb:before{content:"\f24b"}i.icon.diners.club.card:before{content:"\f24c"}i.icon.balance:before{content:"\f24e"}i.icon.hourglass.outline:before,i.icon.hourglass.zero:before{content:"\f250"}i.icon.hourglass.one:before{content:"\f251"}i.icon.hourglass.two:before{content:"\f252"}i.icon.hourglass.three:before{content:"\f253"}i.icon.hourglass.four:before{content:"\f254"}i.icon.grab:before{content:"\f255"}i.icon.hand.victory:before{content:"\f25b"}i.icon.tm:before{content:"\f25c"}i.icon.r.circle:before{content:"\f25d"}i.icon.television:before{content:"\f26c"}i.icon.five.hundred.pixels:before{content:"\f26e"}i.icon.calendar.plus:before{content:"\f271"}i.icon.calendar.minus:before{content:"\f272"}i.icon.calendar.times:before{content:"\f273"}i.icon.calendar.check:before{content:"\f274"}i.icon.factory:before{content:"\f275"}i.icon.commenting:before{content:"\f27a"}i.icon.commenting.outline:before{content:"\f27b"}i.icon.edge:before,i.icon.ms.edge:before{content:"\f282"}i.icon.wordpress.beginner:before{content:"\f297"}i.icon.wordpress.forms:before{content:"\f298"}i.icon.envira:before{content:"\f299"}i.icon.question.circle.outline:before{content:"\f29c"}i.icon.ald:before,i.icon.als:before,i.icon.assistive.listening.devices:before{content:"\f2a2"}i.icon.asl.interpreting:before{content:"\f2a3"}i.icon.deaf:before{content:"\f2a4"}i.icon.american.sign.language.interpreting:before{content:"\f2a3"}i.icon.hard.of.hearing:before{content:"\f2a4"}i.icon.signing:before{content:"\f2a7"}i.icon.new.pied.piper:before{content:"\f2ae"}i.icon.theme.isle:before{content:"\f2b2"}i.icon.google.plus.official:before{content:"\f2b3"}i.icon.fa:before{content:"\f2b4"}.ui.image{position:relative;display:inline-block;vertical-align:middle;max-width:100%;background-color:transparent}img.ui.image{display:block}.ui.image img,.ui.image svg{display:block;max-width:100%;height:auto}.ui.hidden.image,.ui.hidden.images{display:none}.ui.hidden.transition.image,.ui.hidden.transition.images{display:block;visibility:hidden}.ui.disabled.image,.ui.disabled.images{cursor:default;opacity:.45}.ui.inline.image,.ui.inline.image img,.ui.inline.image svg{display:inline-block}.ui.top.aligned.image,.ui.top.aligned.image img,.ui.top.aligned.image svg,.ui.top.aligned.images .image{display:inline-block;vertical-align:top}.ui.middle.aligned.image,.ui.middle.aligned.image img,.ui.middle.aligned.image svg,.ui.middle.aligned.images .image{display:inline-block;vertical-align:middle}.ui.bottom.aligned.image,.ui.bottom.aligned.image img,.ui.bottom.aligned.image svg,.ui.bottom.aligned.images .image{display:inline-block;vertical-align:bottom}.ui.rounded.image,.ui.rounded.image>*,.ui.rounded.images .image,.ui.rounded.images .image>*{border-radius:.3125em}.ui.bordered.image img,.ui.bordered.image svg,.ui.bordered.images .image,.ui.bordered.images img,.ui.bordered.images svg,img.ui.bordered.image{border:1px solid rgba(0,0,0,.1)}.ui.circular.image,.ui.circular.images{overflow:hidden}.ui.circular.image,.ui.circular.image>*,.ui.circular.images .image,.ui.circular.images .image>*{border-radius:500rem}.ui.fluid.image,.ui.fluid.image img,.ui.fluid.image svg,.ui.fluid.images,.ui.fluid.images img,.ui.fluid.images svg{display:block;width:100%;height:auto}.ui.avatar.image,.ui.avatar.image img,.ui.avatar.image svg,.ui.avatar.images .image,.ui.avatar.images img,.ui.avatar.images svg{margin-right:.25em;display:inline-block;width:2em;height:2em;border-radius:500rem}.ui.spaced.image{display:inline-block!important;margin-left:.5em;margin-right:.5em}.ui[class*="left spaced"].image{margin-left:.5em;margin-right:0}.ui[class*="right spaced"].image{margin-left:0;margin-right:.5em}.ui.floated.image,.ui.floated.images{float:left;margin-right:1em;margin-bottom:1em}.ui.right.floated.image,.ui.right.floated.images{float:right;margin-right:0;margin-bottom:1em;margin-left:1em}.ui.floated.image:last-child,.ui.floated.images:last-child{margin-bottom:0}.ui.centered.image,.ui.centered.images{margin-left:auto;margin-right:auto}.ui.mini.image,.ui.mini.images .image,.ui.mini.images img,.ui.mini.images svg{width:35px;height:auto;font-size:.78571429rem}.ui.tiny.image,.ui.tiny.images .image,.ui.tiny.images img,.ui.tiny.images svg{width:80px;height:auto;font-size:.85714286rem}.ui.small.image,.ui.small.images .image,.ui.small.images img,.ui.small.images svg{width:150px;height:auto;font-size:.92857143rem}.ui.medium.image,.ui.medium.images .image,.ui.medium.images img,.ui.medium.images svg{width:300px;height:auto;font-size:1rem}.ui.large.image,.ui.large.images .image,.ui.large.images img,.ui.large.images svg{width:450px;height:auto;font-size:1.14285714rem}.ui.big.image,.ui.big.images .image,.ui.big.images img,.ui.big.images svg{width:600px;height:auto;font-size:1.28571429rem}.ui.huge.image,.ui.huge.images .image,.ui.huge.images img,.ui.huge.images svg{width:800px;height:auto;font-size:1.42857143rem}.ui.massive.image,.ui.massive.images .image,.ui.massive.images img,.ui.massive.images svg{width:960px;height:auto;font-size:1.71428571rem}.ui.images{font-size:0;margin:0 -.25rem}.ui.images .image,.ui.images img,.ui.images svg{display:inline-block;margin:0 .25rem .5rem}.ui.input{position:relative;font-weight:400;font-style:normal;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;color:rgba(0,0,0,.87)}.ui.input input{margin:0;max-width:100%;-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;outline:0;-webkit-tap-highlight-color:rgba(255,255,255,0);text-align:left;line-height:1.2142em;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;padding:.67861429em 1em;background:#FFF;border:1px solid rgba(34,36,38,.15);color:rgba(0,0,0,.87);border-radius:.28571429rem;-webkit-transition:box-shadow .1s ease,border-color .1s ease;transition:box-shadow .1s ease,border-color .1s ease;box-shadow:none}.ui.input input::-webkit-input-placeholder{color:rgba(191,191,191,.87)}.ui.input input::-moz-placeholder{color:rgba(191,191,191,.87)}.ui.input input:-ms-input-placeholder{color:rgba(191,191,191,.87)}.ui.disabled.input,.ui.input input[disabled]{opacity:.45}.ui.disabled.input input,.ui.input input[disabled]{pointer-events:none}.ui.input input:active,.ui.input.down input{border-color:rgba(0,0,0,.3);background:#FAFAFA;color:rgba(0,0,0,.87);box-shadow:none}.ui.loading.loading.input>i.icon:before{position:absolute;content:'';top:50%;left:50%;margin:-.64285714em 0 0 -.64285714em;width:1.28571429em;height:1.28571429em;border-radius:500rem;border:.2em solid rgba(0,0,0,.1)}.ui.loading.loading.input>i.icon:after{position:absolute;content:'';top:50%;left:50%;margin:-.64285714em 0 0 -.64285714em;width:1.28571429em;height:1.28571429em;-webkit-animation:button-spin .6s linear;animation:button-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 transparent transparent;border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent}.ui.input input:focus,.ui.input.focus input{border-color:#85B7D9;background:#FFF;color:rgba(0,0,0,.8);box-shadow:none}.ui.input input:focus::-webkit-input-placeholder,.ui.input.focus input::-webkit-input-placeholder{color:rgba(115,115,115,.87)}.ui.input input:focus::-moz-placeholder,.ui.input.focus input::-moz-placeholder{color:rgba(115,115,115,.87)}.ui.input input:focus:-ms-input-placeholder,.ui.input.focus input:-ms-input-placeholder{color:rgba(115,115,115,.87)}.ui.input.error input{background-color:#FFF6F6;border-color:#E0B4B4;color:#9F3A38;box-shadow:none}.ui.input.error input::-webkit-input-placeholder{color:#e7bdbc}.ui.input.error input::-moz-placeholder{color:#e7bdbc}.ui.input.error input:-ms-input-placeholder{color:#e7bdbc!important}.ui.input.error input:focus::-webkit-input-placeholder{color:#da9796}.ui.input.error input:focus::-moz-placeholder{color:#da9796}.ui.input.error input:focus:-ms-input-placeholder{color:#da9796!important}.ui.transparent.input input{border-color:transparent!important;background-color:transparent!important;padding:0!important;box-shadow:none!important}.ui.transparent.icon.input>i.icon{width:1.1em}.ui.transparent.icon.input>input{padding-left:0!important;padding-right:2em!important}.ui.transparent[class*="left icon"].input>input{padding-left:2em!important;padding-right:0!important}.ui.transparent.inverted.input{color:#FFF}.ui.transparent.inverted.input input{color:inherit}.ui.transparent.inverted.input input::-webkit-input-placeholder{color:rgba(255,255,255,.5)}.ui.transparent.inverted.input input::-moz-placeholder{color:rgba(255,255,255,.5)}.ui.transparent.inverted.input input:-ms-input-placeholder{color:rgba(255,255,255,.5)}.ui.icon.input>i.icon{cursor:default;position:absolute;line-height:1;text-align:center;top:0;right:0;margin:0;height:100%;width:2.67142857em;opacity:.5;border-radius:0 .28571429rem .28571429rem 0;-webkit-transition:opacity .3s ease;transition:opacity .3s ease}.ui.icon.input>i.icon:not(.link){pointer-events:none}.ui.icon.input input{padding-right:2.67142857em!important}.ui.icon.input>i.icon:after,.ui.icon.input>i.icon:before{left:0;position:absolute;text-align:center;top:50%;width:100%;margin-top:-.5em}.ui.icon.input>i.link.icon{cursor:pointer}.ui.icon.input>i.circular.icon{top:.35em;right:.5em}.ui[class*="left icon"].input>i.icon{right:auto;left:1px;border-radius:.28571429rem 0 0 .28571429rem}.ui[class*="left icon"].input>i.circular.icon{right:auto;left:.5em}.ui[class*="left icon"].input>input{padding-left:2.67142857em!important;padding-right:1em!important}.ui.icon.input>input:focus~i.icon{opacity:1}.ui.labeled.input>.label{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;margin:0;font-size:1em}.ui.labeled.input>.label:not(.corner){padding-top:.78571429em;padding-bottom:.78571429em}.ui.labeled.input:not([class*="corner labeled"]) .label:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.ui.labeled.input:not([class*="corner labeled"]) .label:first-child+input{border-top-left-radius:0;border-bottom-left-radius:0;border-left-color:transparent}.ui.labeled.input:not([class*="corner labeled"]) .label:first-child+input:focus{border-left-color:#85B7D9}.ui[class*="right labeled"].input input{border-top-right-radius:0!important;border-bottom-right-radius:0!important;border-right-color:transparent!important}.ui[class*="right labeled"].input input+.label{border-top-left-radius:0;border-bottom-left-radius:0}.ui[class*="right labeled"].input input:focus{border-right-color:#85B7D9!important}.ui.labeled.input .corner.label{top:1px;right:1px;font-size:.64285714em;border-radius:0 .28571429rem 0 0}.ui[class*="corner labeled"]:not([class*="left corner labeled"]).labeled.input input{padding-right:2.5em!important}.ui[class*="corner labeled"].icon.input:not([class*="left corner labeled"])>input{padding-right:3.25em!important}.ui[class*="corner labeled"].icon.input:not([class*="left corner labeled"])>.icon{margin-right:1.25em}.ui[class*="left corner labeled"].labeled.input input{padding-left:2.5em!important}.ui[class*="left corner labeled"].icon.input>input{padding-left:3.25em!important}.ui[class*="left corner labeled"].icon.input>.icon{margin-left:1.25em}.ui.input>.ui.corner.label{top:1px;right:1px}.ui.input>.ui.left.corner.label{right:auto;left:1px}.ui.action.input>.button,.ui.action.input>.buttons{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.ui.action.input>.button,.ui.action.input>.buttons>.button{padding-top:.78571429em;padding-bottom:.78571429em;margin:0}.ui.action.input:not([class*="left action"])>input{border-top-right-radius:0!important;border-bottom-right-radius:0!important;border-right-color:transparent!important}.ui.action.input:not([class*="left action"])>.button:not(:first-child),.ui.action.input:not([class*="left action"])>.buttons:not(:first-child)>.button,.ui.action.input:not([class*="left action"])>.dropdown:not(:first-child){border-radius:0}.ui.action.input:not([class*="left action"])>.button:last-child,.ui.action.input:not([class*="left action"])>.buttons:last-child>.button,.ui.action.input:not([class*="left action"])>.dropdown:last-child{border-radius:0 .28571429rem .28571429rem 0}.ui.action.input:not([class*="left action"]) input:focus{border-right-color:#85B7D9!important}.ui[class*="left action"].input>input{border-top-left-radius:0!important;border-bottom-left-radius:0!important;border-left-color:transparent!important}.ui[class*="left action"].input>.button,.ui[class*="left action"].input>.buttons>.button,.ui[class*="left action"].input>.dropdown{border-radius:0}.ui[class*="left action"].input>.button:first-child,.ui[class*="left action"].input>.buttons:first-child>.button,.ui[class*="left action"].input>.dropdown:first-child{border-radius:.28571429rem 0 0 .28571429rem}.ui[class*="left action"].input>input:focus{border-left-color:#85B7D9!important}.ui.inverted.input input{border:none}.ui.fluid.input{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.ui.fluid.input>input{width:0!important}.ui.mini.input{font-size:.78571429em}.ui.small.input{font-size:.92857143em}.ui.input{font-size:1em}.ui.large.input{font-size:1.14285714em}.ui.big.input{font-size:1.28571429em}.ui.huge.input{font-size:1.42857143em}.ui.massive.input{font-size:1.71428571em}.ui.label{display:inline-block;line-height:1;vertical-align:baseline;margin:0 .14285714em;background-color:#E8E8E8;background-image:none;padding:.5833em .833em;color:rgba(0,0,0,.6);text-transform:none;font-weight:700;border:0 solid transparent;border-radius:.28571429rem;-webkit-transition:background .1s ease;transition:background .1s ease}.ui.label:first-child{margin-left:0}.ui.label:last-child{margin-right:0}a.ui.label{cursor:pointer}.ui.label>a{cursor:pointer;color:inherit;opacity:.5;-webkit-transition:.1s opacity ease;transition:.1s opacity ease}.ui.label>a:hover{opacity:1}.ui.label>img{width:auto!important;vertical-align:middle;height:2.1666em!important}.ui.label>.icon{width:auto;margin:0 .75em 0 0}.ui.label>.detail{display:inline-block;vertical-align:top;font-weight:700;margin-left:1em;opacity:.8}.ui.label>.detail .icon{margin:0 .25em 0 0}.ui.label>.close.icon,.ui.label>.delete.icon{cursor:pointer;margin-right:0;margin-left:.5em;font-size:.92857143em;opacity:.5;-webkit-transition:background .1s ease;transition:background .1s ease}.ui.label>.delete.icon:hover{opacity:1}.ui.labels>.label{margin:0 .5em .5em 0}.ui.header>.ui.label{margin-top:-.29165em}.ui.attached.segment>.ui.top.left.attached.label,.ui.bottom.attached.segment>.ui.top.left.attached.label{border-top-left-radius:0}.ui.attached.segment>.ui.top.right.attached.label,.ui.bottom.attached.segment>.ui.top.right.attached.label{border-top-right-radius:0}.ui.top.attached.segment>.ui.bottom.left.attached.label{border-bottom-left-radius:0}.ui.top.attached.segment>.ui.bottom.right.attached.label{border-bottom-right-radius:0}.ui.top.attached.label+[class*="right floated"]+*,.ui.top.attached.label:first-child+:not(.attached){margin-top:2rem!important}.ui.bottom.attached.label:first-child~:last-child:not(.attached){margin-top:0;margin-bottom:2rem!important}.ui.image.label{width:auto!important;margin-top:0;margin-bottom:0;max-width:9999px;vertical-align:baseline;text-transform:none;background:#E8E8E8;padding:.5833em .833em .5833em .5em;border-radius:.28571429rem;box-shadow:none}.ui.image.label img{display:inline-block;vertical-align:top;height:2.1666em;margin:-.5833em .5em -.5833em -.5em;border-radius:.28571429rem 0 0 .28571429rem}.ui.image.label .detail{background:rgba(0,0,0,.1);margin:-.5833em -.833em -.5833em .5em;padding:.5833em .833em;border-radius:0 .28571429rem .28571429rem 0}.ui.tag.label,.ui.tag.labels .label{margin-left:1em;position:relative;padding-left:1.5em;padding-right:1.5em;border-radius:0 .28571429rem .28571429rem 0;-webkit-transition:none;transition:none}.ui.tag.label:before,.ui.tag.labels .label:before{position:absolute;-webkit-transform:translateY(-50%) translateX(50%) rotate(-45deg);transform:translateY(-50%) translateX(50%) rotate(-45deg);top:50%;right:100%;content:'';background-color:inherit;background-image:none;width:1.56em;height:1.56em;-webkit-transition:none;transition:none}.ui.tag.label:after,.ui.tag.labels .label:after{position:absolute;content:'';top:50%;left:-.25em;margin-top:-.25em;background-color:#FFF!important;width:.5em;height:.5em;box-shadow:0 -1px 1px 0 rgba(0,0,0,.3);border-radius:500rem}.ui.corner.label{position:absolute;top:0;right:0;margin:0;padding:0;text-align:center;border-color:#E8E8E8;width:4em;height:4em;z-index:1;-webkit-transition:border-color .1s ease;transition:border-color .1s ease;background-color:transparent!important}.ui.corner.label:after{position:absolute;content:"";right:0;top:0;z-index:-1;width:0;height:0;background-color:transparent!important;border-top:0 solid transparent;border-right:4em solid transparent;border-bottom:4em solid transparent;border-left:0 solid transparent;border-right-color:inherit;-webkit-transition:border-color .1s ease;transition:border-color .1s ease}.ui.corner.label .icon{cursor:default;position:relative;top:.64285714em;left:.78571429em;font-size:1.14285714em;margin:0}.ui.left.corner.label,.ui.left.corner.label:after{right:auto;left:0}.ui.left.corner.label:after{border-top:4em solid transparent;border-right:4em solid transparent;border-bottom:0 solid transparent;border-left:0 solid transparent;border-top-color:inherit}.ui.left.corner.label .icon{left:-.78571429em}.ui.segment>.ui.corner.label{top:-1px;right:-1px}.ui.segment>.ui.left.corner.label{right:auto;left:-1px}.ui.ribbon.label{position:relative;margin:0;min-width:-webkit-max-content;min-width:-moz-max-content;min-width:max-content;border-radius:0 .28571429rem .28571429rem 0;border-color:rgba(0,0,0,.15)}.ui.ribbon.label:after{position:absolute;content:'';top:100%;left:0;background-color:transparent!important;border-style:solid;border-width:0 1.2em 1.2em 0;border-color:transparent;border-right-color:inherit;width:0;height:0}.ui.ribbon.label{left:calc(-1rem - 1.2em);margin-right:-1.2em;padding-left:calc(1rem + 1.2em);padding-right:1.2em}.ui[class*="right ribbon"].label{left:calc(100% + 1rem + 1.2em);padding-left:1.2em;padding-right:calc(1rem + 1.2em);text-align:left;-webkit-transform:translateX(-100%);transform:translateX(-100%);border-radius:.28571429rem 0 0 .28571429rem}.ui[class*="right ribbon"].label:after{left:auto;right:0;border-style:solid;border-width:1.2em 1.2em 0 0;border-color:transparent;border-top-color:inherit}.ui.card .image>.ribbon.label,.ui.image>.ribbon.label{position:absolute;top:1rem}.ui.card .image>.ui.ribbon.label,.ui.image>.ui.ribbon.label{left:calc(.05rem - 1.2em)}.ui.card .image>.ui[class*="right ribbon"].label,.ui.image>.ui[class*="right ribbon"].label{left:calc(100% + -.05rem + 1.2em);padding-left:.833em}.ui.table td>.ui.ribbon.label{left:calc(-.78571429em - 1.2em)}.ui.table td>.ui[class*="right ribbon"].label{left:calc(100% + .78571429em + 1.2em);padding-left:.833em}.ui.attached.label,.ui[class*="top attached"].label{width:100%;position:absolute;margin:0;top:0;left:0;padding:.75em 1em;border-radius:.21428571rem .21428571rem 0 0}.ui[class*="bottom attached"].label{top:auto;bottom:0;border-radius:0 0 .21428571rem .21428571rem}.ui[class*="top left attached"].label{width:auto;margin-top:0!important;border-radius:.21428571rem 0 .28571429rem}.ui[class*="top right attached"].label{width:auto;left:auto;right:0;border-radius:0 .21428571rem 0 .28571429rem}.ui[class*="bottom left attached"].label{width:auto;top:auto;bottom:0;border-radius:0 .28571429rem 0 .21428571rem}.ui[class*="bottom right attached"].label{top:auto;bottom:0;left:auto;right:0;width:auto;border-radius:.28571429rem 0 .21428571rem}.ui.label.disabled{opacity:.5}a.ui.label:hover,a.ui.labels .label:hover{background-color:#E0E0E0;border-color:#E0E0E0;background-image:none;color:rgba(0,0,0,.8)}.ui.labels a.label:hover:before,a.ui.label:hover:before{color:rgba(0,0,0,.8)}.ui.active.label{background-color:#D0D0D0;border-color:#D0D0D0;background-image:none;color:rgba(0,0,0,.95)}.ui.active.label:before{background-color:#D0D0D0;background-image:none;color:rgba(0,0,0,.95)}a.ui.active.label:hover,a.ui.labels .active.label:hover{background-color:#C8C8C8;border-color:#C8C8C8;background-image:none;color:rgba(0,0,0,.95)}.ui.labels a.active.label:ActiveHover:before,a.ui.active.label:ActiveHover:before{background-color:#C8C8C8;background-image:none;color:rgba(0,0,0,.95)}.ui.label.visible:not(.dropdown),.ui.labels.visible .label{display:inline-block!important}.ui.label.hidden,.ui.labels.hidden .label{display:none!important}.ui.red.label,.ui.red.labels .label{background-color:#DB2828!important;border-color:#DB2828!important;color:#FFF!important}.ui.red.labels .label:hover,a.ui.red.label:hover{background-color:#d01919!important;border-color:#d01919!important;color:#FFF!important}.ui.red.corner.label,.ui.red.corner.label:hover{background-color:transparent!important}.ui.red.ribbon.label{border-color:#b21e1e!important}.ui.basic.red.label{background-color:#FFF!important;color:#DB2828!important;border-color:#DB2828!important}.ui.basic.red.labels a.label:hover,a.ui.basic.red.label:hover{background-color:#FFF!important;color:#d01919!important;border-color:#d01919!important}.ui.orange.label,.ui.orange.labels .label{background-color:#F2711C!important;border-color:#F2711C!important;color:#FFF!important}.ui.orange.labels .label:hover,a.ui.orange.label:hover{background-color:#f26202!important;border-color:#f26202!important;color:#FFF!important}.ui.orange.corner.label,.ui.orange.corner.label:hover{background-color:transparent!important}.ui.orange.ribbon.label{border-color:#cf590c!important}.ui.basic.orange.label{background-color:#FFF!important;color:#F2711C!important;border-color:#F2711C!important}.ui.basic.orange.labels a.label:hover,a.ui.basic.orange.label:hover{background-color:#FFF!important;color:#f26202!important;border-color:#f26202!important}.ui.yellow.label,.ui.yellow.labels .label{background-color:#FBBD08!important;border-color:#FBBD08!important;color:#FFF!important}.ui.yellow.labels .label:hover,a.ui.yellow.label:hover{background-color:#eaae00!important;border-color:#eaae00!important;color:#FFF!important}.ui.yellow.corner.label,.ui.yellow.corner.label:hover{background-color:transparent!important}.ui.yellow.ribbon.label{border-color:#cd9903!important}.ui.basic.yellow.label{background-color:#FFF!important;color:#FBBD08!important;border-color:#FBBD08!important}.ui.basic.yellow.labels a.label:hover,a.ui.basic.yellow.label:hover{background-color:#FFF!important;color:#eaae00!important;border-color:#eaae00!important}.ui.olive.label,.ui.olive.labels .label{background-color:#B5CC18!important;border-color:#B5CC18!important;color:#FFF!important}.ui.olive.labels .label:hover,a.ui.olive.label:hover{background-color:#a7bd0d!important;border-color:#a7bd0d!important;color:#FFF!important}.ui.olive.corner.label,.ui.olive.corner.label:hover{background-color:transparent!important}.ui.olive.ribbon.label{border-color:#198f35!important}.ui.basic.olive.label{background-color:#FFF!important;color:#B5CC18!important;border-color:#B5CC18!important}.ui.basic.olive.labels a.label:hover,a.ui.basic.olive.label:hover{background-color:#FFF!important;color:#a7bd0d!important;border-color:#a7bd0d!important}.ui.green.label,.ui.green.labels .label{background-color:#21BA45!important;border-color:#21BA45!important;color:#FFF!important}.ui.green.labels .label:hover,a.ui.green.label:hover{background-color:#16ab39!important;border-color:#16ab39!important;color:#FFF!important}.ui.green.corner.label,.ui.green.corner.label:hover{background-color:transparent!important}.ui.green.ribbon.label{border-color:#198f35!important}.ui.basic.green.label{background-color:#FFF!important;color:#21BA45!important;border-color:#21BA45!important}.ui.basic.green.labels a.label:hover,a.ui.basic.green.label:hover{background-color:#FFF!important;color:#16ab39!important;border-color:#16ab39!important}.ui.teal.label,.ui.teal.labels .label{background-color:#00B5AD!important;border-color:#00B5AD!important;color:#FFF!important}.ui.teal.labels .label:hover,a.ui.teal.label:hover{background-color:#009c95!important;border-color:#009c95!important;color:#FFF!important}.ui.teal.corner.label,.ui.teal.corner.label:hover{background-color:transparent!important}.ui.teal.ribbon.label{border-color:#00827c!important}.ui.basic.teal.label{background-color:#FFF!important;color:#00B5AD!important;border-color:#00B5AD!important}.ui.basic.teal.labels a.label:hover,a.ui.basic.teal.label:hover{background-color:#FFF!important;color:#009c95!important;border-color:#009c95!important}.ui.blue.label,.ui.blue.labels .label{background-color:#2185D0!important;border-color:#2185D0!important;color:#FFF!important}.ui.blue.labels .label:hover,a.ui.blue.label:hover{background-color:#1678c2!important;border-color:#1678c2!important;color:#FFF!important}.ui.blue.corner.label,.ui.blue.corner.label:hover{background-color:transparent!important}.ui.blue.ribbon.label{border-color:#1a69a4!important}.ui.basic.blue.label{background-color:#FFF!important;color:#2185D0!important;border-color:#2185D0!important}.ui.basic.blue.labels a.label:hover,a.ui.basic.blue.label:hover{background-color:#FFF!important;color:#1678c2!important;border-color:#1678c2!important}.ui.violet.label,.ui.violet.labels .label{background-color:#6435C9!important;border-color:#6435C9!important;color:#FFF!important}.ui.violet.labels .label:hover,a.ui.violet.label:hover{background-color:#5829bb!important;border-color:#5829bb!important;color:#FFF!important}.ui.violet.corner.label,.ui.violet.corner.label:hover{background-color:transparent!important}.ui.violet.ribbon.label{border-color:#502aa1!important}.ui.basic.violet.label{background-color:#FFF!important;color:#6435C9!important;border-color:#6435C9!important}.ui.basic.violet.labels a.label:hover,a.ui.basic.violet.label:hover{background-color:#FFF!important;color:#5829bb!important;border-color:#5829bb!important}.ui.purple.label,.ui.purple.labels .label{background-color:#A333C8!important;border-color:#A333C8!important;color:#FFF!important}.ui.purple.labels .label:hover,a.ui.purple.label:hover{background-color:#9627ba!important;border-color:#9627ba!important;color:#FFF!important}.ui.purple.corner.label,.ui.purple.corner.label:hover{background-color:transparent!important}.ui.purple.ribbon.label{border-color:#82299f!important}.ui.basic.purple.label{background-color:#FFF!important;color:#A333C8!important;border-color:#A333C8!important}.ui.basic.purple.labels a.label:hover,a.ui.basic.purple.label:hover{background-color:#FFF!important;color:#9627ba!important;border-color:#9627ba!important}.ui.pink.label,.ui.pink.labels .label{background-color:#E03997!important;border-color:#E03997!important;color:#FFF!important}.ui.pink.labels .label:hover,a.ui.pink.label:hover{background-color:#e61a8d!important;border-color:#e61a8d!important;color:#FFF!important}.ui.pink.corner.label,.ui.pink.corner.label:hover{background-color:transparent!important}.ui.pink.ribbon.label{border-color:#c71f7e!important}.ui.basic.pink.label{background-color:#FFF!important;color:#E03997!important;border-color:#E03997!important}.ui.basic.pink.labels a.label:hover,a.ui.basic.pink.label:hover{background-color:#FFF!important;color:#e61a8d!important;border-color:#e61a8d!important}.ui.brown.label,.ui.brown.labels .label{background-color:#A5673F!important;border-color:#A5673F!important;color:#FFF!important}.ui.brown.labels .label:hover,a.ui.brown.label:hover{background-color:#975b33!important;border-color:#975b33!important;color:#FFF!important}.ui.brown.corner.label,.ui.brown.corner.label:hover{background-color:transparent!important}.ui.brown.ribbon.label{border-color:#805031!important}.ui.basic.brown.label{background-color:#FFF!important;color:#A5673F!important;border-color:#A5673F!important}.ui.basic.brown.labels a.label:hover,a.ui.basic.brown.label:hover{background-color:#FFF!important;color:#975b33!important;border-color:#975b33!important}.ui.grey.label,.ui.grey.labels .label{background-color:#767676!important;border-color:#767676!important;color:#FFF!important}.ui.grey.labels .label:hover,a.ui.grey.label:hover{background-color:#838383!important;border-color:#838383!important;color:#FFF!important}.ui.grey.corner.label,.ui.grey.corner.label:hover{background-color:transparent!important}.ui.grey.ribbon.label{border-color:#805031!important}.ui.basic.grey.label{background-color:#FFF!important;color:#767676!important;border-color:#767676!important}.ui.basic.grey.labels a.label:hover,a.ui.basic.grey.label:hover{background-color:#FFF!important;color:#838383!important;border-color:#838383!important}.ui.black.label,.ui.black.labels .label{background-color:#1B1C1D!important;border-color:#1B1C1D!important;color:#FFF!important}.ui.black.labels .label:hover,a.ui.black.label:hover{background-color:#27292a!important;border-color:#27292a!important;color:#FFF!important}.ui.black.corner.label,.ui.black.corner.label:hover{background-color:transparent!important}.ui.black.ribbon.label{border-color:#805031!important}.ui.basic.black.label{background-color:#FFF!important;color:#1B1C1D!important;border-color:#1B1C1D!important}.ui.basic.black.labels a.label:hover,a.ui.basic.black.label:hover{background-color:#FFF!important;color:#27292a!important;border-color:#27292a!important}.ui.basic.label{background:#FFF;border:1px solid rgba(34,36,38,.15);color:rgba(0,0,0,.87);box-shadow:none}a.ui.basic.label:hover{text-decoration:none;background:#FFF;color:#1e70bf;box-shadow:1px solid rgba(34,36,38,.15);box-shadow:none}.ui.basic.pointing.label:before{border-color:inherit}.ui.fluid.labels>.label,.ui.label.fluid{width:100%;box-sizing:border-box}.ui.inverted.label,.ui.inverted.labels .label{color:rgba(255,255,255,.9)!important}.ui.horizontal.label,.ui.horizontal.labels .label{margin:0 .5em 0 0;padding:.4em .833em;min-width:3em;text-align:center}.ui.circular.label,.ui.circular.labels .label{min-width:2em;min-height:2em;padding:.5em!important;line-height:1em;text-align:center;border-radius:500rem}.ui.empty.circular.label,.ui.empty.circular.labels .label{min-width:0;min-height:0;overflow:hidden;width:.5em;height:.5em;vertical-align:baseline}.ui.pointing.label{position:relative}.ui.attached.pointing.label{position:absolute}.ui.pointing.label:before{background-color:inherit;border-style:solid;border-color:inherit;position:absolute;content:'';-webkit-transform:rotate(45deg);transform:rotate(45deg);background-image:none;z-index:2;width:.6666em;height:.6666em;-webkit-transition:background .1s ease;transition:background .1s ease}.ui.pointing.label,.ui[class*="pointing above"].label{margin-top:1em}.ui.pointing.label:before,.ui[class*="pointing above"].label:before{border-width:1px 0 0 1px;-webkit-transform:translateX(-50%) translateY(-50%) rotate(45deg);transform:translateX(-50%) translateY(-50%) rotate(45deg);top:0;left:50%}.ui[class*="bottom pointing"].label,.ui[class*="pointing below"].label{margin-top:0;margin-bottom:1em}.ui[class*="bottom pointing"].label:before,.ui[class*="pointing below"].label:before{border-width:0 1px 1px 0;right:auto;-webkit-transform:translateX(-50%) translateY(-50%) rotate(45deg);transform:translateX(-50%) translateY(-50%) rotate(45deg);top:100%;left:50%}.ui[class*="left pointing"].label{margin-top:0;margin-left:.6666em}.ui[class*="left pointing"].label:before{border-width:0 0 1px 1px;-webkit-transform:translateX(-50%) translateY(-50%) rotate(45deg);transform:translateX(-50%) translateY(-50%) rotate(45deg);bottom:auto;right:auto;top:50%;left:0}.ui[class*="right pointing"].label{margin-top:0;margin-right:.6666em}.ui[class*="right pointing"].label:before{border-width:1px 1px 0 0;-webkit-transform:translateX(50%) translateY(-50%) rotate(45deg);transform:translateX(50%) translateY(-50%) rotate(45deg);top:50%;right:0;bottom:auto;left:auto}.ui.basic.pointing.label:before,.ui.basic[class*="pointing above"].label:before{margin-top:-1px}.ui.basic[class*="bottom pointing"].label:before,.ui.basic[class*="pointing below"].label:before{bottom:auto;top:100%;margin-top:1px}.ui.basic[class*="left pointing"].label:before{top:50%;left:-1px}.ui.basic[class*="right pointing"].label:before{top:50%;right:-1px}.ui.floating.label{position:absolute;z-index:100;top:-1em;left:100%;margin:0 0 0 -1.5em!important}.ui.mini.label,.ui.mini.labels .label{font-size:.64285714rem}.ui.tiny.label,.ui.tiny.labels .label{font-size:.71428571rem}.ui.small.label,.ui.small.labels .label{font-size:.78571429rem}.ui.label,.ui.labels .label{font-size:.85714286rem}.ui.large.label,.ui.large.labels .label{font-size:1rem}.ui.big.label,.ui.big.labels .label{font-size:1.28571429rem}.ui.huge.label,.ui.huge.labels .label{font-size:1.42857143rem}.ui.massive.label,.ui.massive.labels .label{font-size:1.71428571rem}.ui.list,ol.ui.list,ul.ui.list{list-style-type:none;margin:1em 0;padding:0}.ui.list:first-child,ol.ui.list:first-child,ul.ui.list:first-child{margin-top:0;padding-top:0}.ui.list:last-child,ol.ui.list:last-child,ul.ui.list:last-child{margin-bottom:0;padding-bottom:0}.ui.list .list>.item,.ui.list>.item,ol.ui.list li,ul.ui.list li{display:list-item;table-layout:fixed;list-style-type:none;list-style-position:outside;padding:.21428571em 0;line-height:1.14285714em}.ui.list>.item:after,.ui.list>.list>.item,ol.ui.list>li:first-child:after,ul.ui.list>li:first-child:after{content:'';display:block;height:0;clear:both;visibility:hidden}.ui.list .list>.item:first-child,.ui.list>.item:first-child,ol.ui.list li:first-child,ul.ui.list li:first-child{padding-top:0}.ui.list .list>.item:last-child,.ui.list>.item:last-child,ol.ui.list li:last-child,ul.ui.list li:last-child{padding-bottom:0}.ui.list .list,ol.ui.list ol,ul.ui.list ul{clear:both;margin:0;padding:.75em 0 .25em .5em}.ui.list .list>.item,ol.ui.list ol li,ul.ui.list ul li{padding:.14285714em 0;line-height:inherit}.ui.list .list>.item>i.icon,.ui.list>.item>i.icon{display:table-cell;margin:0;padding-top:.07142857em;padding-right:.28571429em;vertical-align:top;-webkit-transition:color .1s ease;transition:color .1s ease}.ui.list .list>.item>i.icon:only-child,.ui.list>.item>i.icon:only-child{display:inline-block;vertical-align:top}.ui.list .list>.item>.image,.ui.list>.item>.image{display:table-cell;background-color:transparent;margin:0;vertical-align:top}.ui.list .list>.item>.image:not(:only-child):not(img),.ui.list>.item>.image:not(:only-child):not(img){padding-right:.5em}.ui.list .list>.item>.image img,.ui.list>.item>.image img{vertical-align:top}.ui.list .list>.item>.image:only-child,.ui.list .list>.item>img.image,.ui.list>.item>.image:only-child,.ui.list>.item>img.image{display:inline-block}.ui.list .list>.item>.content,.ui.list>.item>.content{line-height:1.14285714em}.ui.list .list>.item>.icon+.content,.ui.list .list>.item>.image+.content,.ui.list>.item>.icon+.content,.ui.list>.item>.image+.content{display:table-cell;padding:0 0 0 .5em;vertical-align:top}.ui.list .list>.item>img.image+.content,.ui.list>.item>img.image+.content{display:inline-block}.ui.list .list>.item>.content>.list,.ui.list>.item>.content>.list{margin-left:0;padding-left:0}.ui.list .list>.item .header,.ui.list>.item .header{display:block;margin:0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:700;color:rgba(0,0,0,.87)}.ui.list .list>.item .description,.ui.list>.item .description{display:block;color:rgba(0,0,0,.7)}.ui.list .list>.item a,.ui.list>.item a{cursor:pointer}.ui.list .list>a.item,.ui.list>a.item{cursor:pointer;color:#4183C4}.ui.list .list>a.item:hover,.ui.list>a.item:hover{color:#1e70bf}.ui.list .list>a.item i.icon,.ui.list>a.item i.icon{color:rgba(0,0,0,.4)}.ui.list .list>.item a.header,.ui.list>.item a.header{cursor:pointer;color:#4183C4!important}.ui.list .list>.item a.header:hover,.ui.list>.item a.header:hover{color:#1e70bf!important}.ui[class*="left floated"].list{float:left}.ui[class*="right floated"].list{float:right}.ui.list .list>.item [class*="left floated"],.ui.list>.item [class*="left floated"]{float:left;margin:0 1em 0 0}.ui.list .list>.item [class*="right floated"],.ui.list>.item [class*="right floated"]{float:right;margin:0 0 0 1em}.ui.menu .ui.list .list>.item,.ui.menu .ui.list>.item{display:list-item;table-layout:fixed;background-color:transparent;list-style-type:none;list-style-position:outside;padding:.21428571em 0;line-height:1.14285714em}.ui.menu .ui.list .list>.item:before,.ui.menu .ui.list>.item:before{border:none;background:0 0}.ui.menu .ui.list .list>.item:first-child,.ui.menu .ui.list>.item:first-child{padding-top:0}.ui.menu .ui.list .list>.item:last-child,.ui.menu .ui.list>.item:last-child{padding-bottom:0}.ui.horizontal.list{display:inline-block;font-size:0}.ui.horizontal.list>.item{display:inline-block;margin-left:1em;font-size:1rem}.ui.horizontal.list:not(.celled)>.item:first-child{margin-left:0!important;padding-left:0!important}.ui.horizontal.list .list{padding-left:0;padding-bottom:0}.ui.horizontal.list .list>.item>.content,.ui.horizontal.list .list>.item>.icon,.ui.horizontal.list .list>.item>.image,.ui.horizontal.list>.item>.content,.ui.horizontal.list>.item>.icon,.ui.horizontal.list>.item>.image{vertical-align:middle}.ui.horizontal.list>.item:first-child,.ui.horizontal.list>.item:last-child{padding-top:.21428571em;padding-bottom:.21428571em}.ui.horizontal.list>.item>i.icon{margin:0;padding:0 .25em 0 0}.ui.horizontal.list>.item>.icon,.ui.horizontal.list>.item>.icon+.content{float:none;display:inline-block}.ui.list .list>.disabled.item,.ui.list>.disabled.item{pointer-events:none;color:rgba(40,40,40,.3)!important}.ui.inverted.list .list>.disabled.item,.ui.inverted.list>.disabled.item{color:rgba(225,225,225,.3)!important}.ui.list .list>a.item:hover .icon,.ui.list>a.item:hover .icon{color:rgba(0,0,0,.87)}.ui.inverted.list .list>a.item>.icon,.ui.inverted.list>a.item>.icon{color:rgba(255,255,255,.7)}.ui.inverted.list .list>.item .header,.ui.inverted.list>.item .header{color:rgba(255,255,255,.9)}.ui.inverted.list .list>.item .description,.ui.inverted.list>.item .description{color:rgba(255,255,255,.7)}.ui.inverted.list .list>a.item,.ui.inverted.list>a.item{cursor:pointer;color:rgba(255,255,255,.9)}.ui.inverted.list .list>a.item:hover,.ui.inverted.list>a.item:hover{color:#1e70bf}.ui.inverted.list .item a:not(.ui){color:rgba(255,255,255,.9)!important}.ui.inverted.list .item a:not(.ui):hover{color:#1e70bf!important}.ui.list [class*="top aligned"],.ui.list[class*="top aligned"] .content,.ui.list[class*="top aligned"] .image{vertical-align:top!important}.ui.list [class*="middle aligned"],.ui.list[class*="middle aligned"] .content,.ui.list[class*="middle aligned"] .image{vertical-align:middle!important}.ui.list [class*="bottom aligned"],.ui.list[class*="bottom aligned"] .content,.ui.list[class*="bottom aligned"] .image{vertical-align:bottom!important}.ui.link.list .item,.ui.link.list .item a:not(.ui),.ui.link.list a.item{color:rgba(0,0,0,.4);-webkit-transition:.1s color ease;transition:.1s color ease}.ui.link.list .item a:not(.ui):hover,.ui.link.list a.item:hover{color:rgba(0,0,0,.8)}.ui.link.list .item a:not(.ui):active,.ui.link.list a.item:active{color:rgba(0,0,0,.9)}.ui.link.list .active.item,.ui.link.list .active.item a:not(.ui){color:rgba(0,0,0,.95)}.ui.inverted.link.list .item,.ui.inverted.link.list .item a:not(.ui),.ui.inverted.link.list a.item{color:rgba(255,255,255,.5)}.ui.inverted.link.list .active.item a:not(.ui),.ui.inverted.link.list .item a:not(.ui):active,.ui.inverted.link.list .item a:not(.ui):hover,.ui.inverted.link.list a.active.item,.ui.inverted.link.list a.item:active,.ui.inverted.link.list a.item:hover{color:#fff}.ui.selection.list .list>.item,.ui.selection.list>.item{cursor:pointer;background:0 0;padding:.5em;margin:0;color:rgba(0,0,0,.4);border-radius:.5em;-webkit-transition:.1s color ease,.1s padding-left ease,.1s background-color ease;transition:.1s color ease,.1s padding-left ease,.1s background-color ease}.ui.selection.list .list>.item:last-child,.ui.selection.list>.item:last-child{margin-bottom:0}.ui.selection.list.list>.item:hover,.ui.selection.list>.item:hover{background:rgba(0,0,0,.03);color:rgba(0,0,0,.8)}.ui.selection.list .list>.item:active,.ui.selection.list>.item:active{background:rgba(0,0,0,.05);color:rgba(0,0,0,.9)}.ui.selection.list .list>.item.active,.ui.selection.list>.item.active{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95)}.ui.inverted.selection.list>.item{background:0 0;color:rgba(255,255,255,.5)}.ui.inverted.selection.list>.item:hover{background:rgba(255,255,255,.02);color:#fff}.ui.inverted.selection.list>.item.active,.ui.inverted.selection.list>.item:active{background:rgba(255,255,255,.08);color:#fff}.ui.celled.selection.list .list>.item,.ui.celled.selection.list>.item,.ui.divided.selection.list .list>.item,.ui.divided.selection.list>.item{border-radius:0}.ui.animated.list>.item{-webkit-transition:.25s color ease .1s,.25s padding-left ease .1s,.25s background-color ease .1s;transition:.25s color ease .1s,.25s padding-left ease .1s,.25s background-color ease .1s}.ui.animated.list:not(.horizontal)>.item:hover{padding-left:1em}.ui.fitted.list:not(.selection) .list>.item,.ui.fitted.list:not(.selection)>.item{padding-left:0;padding-right:0}.ui.fitted.selection.list .list>.item,.ui.fitted.selection.list>.item{margin-left:-.5em;margin-right:-.5em}.ui.bulleted.list,ul.ui.list{margin-left:1.25rem}.ui.bulleted.list .list>.item,.ui.bulleted.list>.item,ul.ui.list li{position:relative}.ui.bulleted.list .list>.item:before,.ui.bulleted.list>.item:before,ul.ui.list li:before{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none;position:absolute;top:auto;left:auto;font-weight:400;margin-left:-1.25rem;content:'•';opacity:1;color:inherit;vertical-align:top}.ui.bulleted.list .list>a.item:before,.ui.bulleted.list>a.item:before,ul.ui.list li:before{color:rgba(0,0,0,.87)}.ui.bulleted.list .list,ul.ui.list ul{padding-left:1.25rem}.ui.horizontal.bulleted.list,ul.ui.horizontal.bulleted.list{margin-left:0}.ui.horizontal.bulleted.list>.item,ul.ui.horizontal.bulleted.list li{margin-left:1.75rem}.ui.horizontal.bulleted.list>.item:first-child,ul.ui.horizontal.bulleted.list li:first-child{margin-left:0}.ui.horizontal.bulleted.list>.item::before,ul.ui.horizontal.bulleted.list li::before{color:rgba(0,0,0,.87)}.ui.horizontal.bulleted.list>.item:first-child::before,ul.ui.horizontal.bulleted.list li:first-child::before{display:none}.ui.ordered.list,.ui.ordered.list .list,ol.ui.list,ol.ui.list ol{counter-reset:ordered;margin-left:1.25rem;list-style-type:none}.ui.ordered.list .list>.item,.ui.ordered.list>.item,ol.ui.list li{list-style-type:none;position:relative}.ui.ordered.list .list>.item:before,.ui.ordered.list>.item:before,ol.ui.list li:before{position:absolute;top:auto;left:auto;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none;margin-left:-1.25rem;counter-increment:ordered;content:counters(ordered,".") " ";text-align:right;color:rgba(0,0,0,.87);vertical-align:middle;opacity:.8}.ui.ordered.inverted.list .list>.item:before,.ui.ordered.inverted.list>.item:before,ol.ui.inverted.list li:before{color:rgba(255,255,255,.7)}.ui.ordered.list>.item[data-value],.ui.ordered.list>.list>.item[data-value]{content:attr(data-value)}ol.ui.list li[value]:before{content:attr(value)}.ui.ordered.list .list,ol.ui.list ol{margin-left:1em}.ui.ordered.list .list>.item:before,ol.ui.list ol li:before{margin-left:-2em}.ui.ordered.horizontal.list,ol.ui.horizontal.list{margin-left:0}.ui.ordered.horizontal.list .list>.item:before,.ui.ordered.horizontal.list>.item:before,ol.ui.horizontal.list li:before{position:static;margin:0 .5em 0 0}.ui.divided.list>.item{border-top:1px solid rgba(34,36,38,.15)}.ui.divided.list .item .list>.item,.ui.divided.list .list>.item,.ui.divided.list .list>.item:first-child,.ui.divided.list>.item:first-child{border-top:none}.ui.divided.list:not(.horizontal) .list>.item:first-child{border-top-width:1px}.ui.divided.bulleted.list .list,.ui.divided.bulleted.list:not(.horizontal){margin-left:0;padding-left:0}.ui.divided.bulleted.list>.item:not(.horizontal){padding-left:1.25rem}.ui.divided.ordered.list{margin-left:0}.ui.divided.ordered.list .list>.item,.ui.divided.ordered.list>.item{padding-left:1.25rem}.ui.divided.ordered.list .item .list{margin-left:0;margin-right:0;padding-bottom:.21428571em}.ui.divided.ordered.list .item .list>.item{padding-left:1em}.ui.divided.selection.list .list>.item,.ui.divided.selection.list>.item{margin:0;border-radius:0}.ui.divided.horizontal.list{margin-left:0}.ui.divided.horizontal.list>.item:not(:first-child){padding-left:.5em}.ui.divided.horizontal.list>.item:not(:last-child){padding-right:.5em}.ui.divided.horizontal.list>.item{border-top:none;border-left:1px solid rgba(34,36,38,.15);margin:0;line-height:.6}.ui.horizontal.divided.list>.item:first-child{border-left:none}.ui.divided.inverted.horizontal.list>.item,.ui.divided.inverted.list>.item,.ui.divided.inverted.list>.list{border-color:rgba(255,255,255,.1)}.ui.celled.list>.item,.ui.celled.list>.list{border-top:1px solid rgba(34,36,38,.15);padding-left:.5em;padding-right:.5em}.ui.celled.list>.item:last-child{border-bottom:1px solid rgba(34,36,38,.15)}.ui.celled.list>.item:first-child,.ui.celled.list>.item:last-child{padding-top:.21428571em;padding-bottom:.21428571em}.ui.celled.list .item .list>.item{border-width:0}.ui.celled.list .list>.item:first-child{border-top-width:0}.ui.celled.bulleted.list{margin-left:0}.ui.celled.bulleted.list .list>.item,.ui.celled.bulleted.list>.item{padding-left:1.25rem}.ui.celled.bulleted.list .item .list{margin-left:-1.25rem;margin-right:-1.25rem;padding-bottom:.21428571em}.ui.celled.ordered.list{margin-left:0}.ui.celled.ordered.list .list>.item,.ui.celled.ordered.list>.item{padding-left:1.25rem}.ui.celled.ordered.list .item .list{margin-left:0;margin-right:0;padding-bottom:.21428571em}.ui.celled.ordered.list .list>.item{padding-left:1em}.ui.horizontal.celled.list{margin-left:0}.ui.horizontal.celled.list .list>.item,.ui.horizontal.celled.list>.item{border-top:none;border-left:1px solid rgba(34,36,38,.15);margin:0;padding-left:.5em;padding-right:.5em;line-height:.6}.ui.horizontal.celled.list .list>.item:last-child,.ui.horizontal.celled.list>.item:last-child{border-bottom:none;border-right:1px solid rgba(34,36,38,.15)}.ui.celled.inverted.horizontal.list .list>.item,.ui.celled.inverted.horizontal.list>.item,.ui.celled.inverted.list>.item,.ui.celled.inverted.list>.list{border-color:1px solid rgba(255,255,255,.1)}.ui.relaxed.list:not(.horizontal)>.item:not(:first-child){padding-top:.42857143em}.ui.relaxed.list:not(.horizontal)>.item:not(:last-child){padding-bottom:.42857143em}.ui.horizontal.relaxed.list .list>.item:not(:first-child),.ui.horizontal.relaxed.list>.item:not(:first-child){padding-left:1rem}.ui.horizontal.relaxed.list .list>.item:not(:last-child),.ui.horizontal.relaxed.list>.item:not(:last-child){padding-right:1rem}.ui[class*="very relaxed"].list:not(.horizontal)>.item:not(:first-child){padding-top:.85714286em}.ui[class*="very relaxed"].list:not(.horizontal)>.item:not(:last-child){padding-bottom:.85714286em}.ui.horizontal[class*="very relaxed"].list .list>.item:not(:first-child),.ui.horizontal[class*="very relaxed"].list>.item:not(:first-child){padding-left:1.5rem}.ui.horizontal[class*="very relaxed"].list .list>.item:not(:last-child),.ui.horizontal[class*="very relaxed"].list>.item:not(:last-child){padding-right:1.5rem}.ui.mini.list{font-size:.78571429em}.ui.tiny.list{font-size:.85714286em}.ui.small.list{font-size:.92857143em}.ui.list{font-size:1em}.ui.large.list{font-size:1.14285714em}.ui.big.list{font-size:1.28571429em}.ui.huge.list{font-size:1.42857143em}.ui.massive.list{font-size:1.71428571em}.ui.mini.horizontal.list .list>.item,.ui.mini.horizontal.list>.item{font-size:.78571429rem}.ui.tiny.horizontal.list .list>.item,.ui.tiny.horizontal.list>.item{font-size:.85714286rem}.ui.small.horizontal.list .list>.item,.ui.small.horizontal.list>.item{font-size:.92857143rem}.ui.horizontal.list .list>.item,.ui.horizontal.list>.item{font-size:1rem}.ui.large.horizontal.list .list>.item,.ui.large.horizontal.list>.item{font-size:1.14285714rem}.ui.big.horizontal.list .list>.item,.ui.big.horizontal.list>.item{font-size:1.28571429rem}.ui.huge.horizontal.list .list>.item,.ui.huge.horizontal.list>.item{font-size:1.42857143rem}.ui.massive.horizontal.list .list>.item,.ui.massive.horizontal.list>.item{font-size:1.71428571rem}.ui.loader{display:none;position:absolute;top:50%;left:50%;margin:0;text-align:center;z-index:1000;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.ui.loader:before{position:absolute;content:'';top:0;left:50%;border-radius:500rem;border:.2em solid rgba(0,0,0,.1)}.ui.loader:after{position:absolute;content:'';top:0;left:50%;-webkit-animation:loader .6s linear;animation:loader .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 transparent transparent;border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent}@-webkit-keyframes loader{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loader{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ui.mini.loader:after,.ui.mini.loader:before{width:1rem;height:1rem;margin:0 0 0 -.5rem}.ui.tiny.loader:after,.ui.tiny.loader:before{width:1.14285714rem;height:1.14285714rem;margin:0 0 0 -.57142857rem}.ui.small.loader:after,.ui.small.loader:before{width:1.71428571rem;height:1.71428571rem;margin:0 0 0 -.85714286rem}.ui.loader:after,.ui.loader:before{width:2.28571429rem;height:2.28571429rem;margin:0 0 0 -1.14285714rem}.ui.large.loader:after,.ui.large.loader:before{width:3.42857143rem;height:3.42857143rem;margin:0 0 0 -1.71428571rem}.ui.big.loader:after,.ui.big.loader:before{width:3.71428571rem;height:3.71428571rem;margin:0 0 0 -1.85714286rem}.ui.huge.loader:after,.ui.huge.loader:before{width:4.14285714rem;height:4.14285714rem;margin:0 0 0 -2.07142857rem}.ui.massive.loader:after,.ui.massive.loader:before{width:4.57142857rem;height:4.57142857rem;margin:0 0 0 -2.28571429rem}.ui.dimmer .loader{display:block}.ui.dimmer .ui.loader{color:rgba(255,255,255,.9)}.ui.dimmer .ui.loader:before{border-color:rgba(255,255,255,.15)}.ui.dimmer .ui.loader:after{border-color:#FFF transparent transparent}.ui.inverted.dimmer .ui.loader{color:rgba(0,0,0,.87)}.ui.inverted.dimmer .ui.loader:before{border-color:rgba(0,0,0,.1)}.ui.inverted.dimmer .ui.loader:after{border-color:#767676 transparent transparent}.ui.text.loader{width:auto!important;height:auto!important;text-align:center;font-style:normal}.ui.indeterminate.loader:after{-webkit-animation-direction:reverse;animation-direction:reverse;-webkit-animation-duration:1.2s;animation-duration:1.2s}.ui.loader.active,.ui.loader.visible{display:block}.ui.loader.disabled,.ui.loader.hidden{display:none}.ui.inverted.dimmer .ui.mini.loader,.ui.mini.loader{width:1rem;height:1rem;font-size:.78571429em}.ui.inverted.dimmer .ui.tiny.loader,.ui.tiny.loader{width:1.14285714rem;height:1.14285714rem;font-size:.85714286em}.ui.inverted.dimmer .ui.small.loader,.ui.small.loader{width:1.71428571rem;height:1.71428571rem;font-size:.92857143em}.ui.inverted.dimmer .ui.loader,.ui.loader{width:2.28571429rem;height:2.28571429rem;font-size:1em}.ui.inverted.dimmer .ui.large.loader,.ui.large.loader{width:3.42857143rem;height:3.42857143rem;font-size:1.14285714em}.ui.big.loader,.ui.inverted.dimmer .ui.big.loader{width:3.71428571rem;height:3.71428571rem;font-size:1.28571429em}.ui.huge.loader,.ui.inverted.dimmer .ui.huge.loader{width:4.14285714rem;height:4.14285714rem;font-size:1.42857143em}.ui.inverted.dimmer .ui.massive.loader,.ui.massive.loader{width:4.57142857rem;height:4.57142857rem;font-size:1.71428571em}.ui.mini.text.loader{min-width:1rem;padding-top:1.78571429rem}.ui.tiny.text.loader{min-width:1.14285714rem;padding-top:1.92857143rem}.ui.small.text.loader{min-width:1.71428571rem;padding-top:2.5rem}.ui.text.loader{min-width:2.28571429rem;padding-top:3.07142857rem}.ui.large.text.loader{min-width:3.42857143rem;padding-top:4.21428571rem}.ui.big.text.loader{min-width:3.71428571rem;padding-top:4.5rem}.ui.huge.text.loader{min-width:4.14285714rem;padding-top:4.92857143rem}.ui.massive.text.loader{min-width:4.57142857rem;padding-top:5.35714286rem}.ui.inverted.loader{color:rgba(255,255,255,.9)}.ui.inverted.loader:before{border-color:rgba(255,255,255,.15)}.ui.inverted.loader:after{border-top-color:#FFF}.ui.inline.loader{position:relative;vertical-align:middle;margin:0;left:0;top:0;-webkit-transform:none;transform:none}.ui.inline.loader.active,.ui.inline.loader.visible{display:inline-block}.ui.centered.inline.loader.active,.ui.centered.inline.loader.visible{display:block;margin-left:auto;margin-right:auto}.ui.rail{position:absolute;top:0;width:300px;height:100%}.ui.left.rail{left:auto;right:100%;padding:0 2rem 0 0;margin:0 2rem 0 0}.ui.right.rail{left:100%;right:auto;padding:0 0 0 2rem;margin:0 0 0 2rem}.ui.left.internal.rail{left:0;right:auto;padding:0 0 0 2rem;margin:0 0 0 2rem}.ui.right.internal.rail{left:auto;right:0;padding:0 2rem 0 0;margin:0 2rem 0 0}.ui.dividing.rail{width:302.5px}.ui.left.dividing.rail{padding:0 2.5rem 0 0;margin:0 2.5rem 0 0;border-right:1px solid rgba(34,36,38,.15)}.ui.right.dividing.rail{border-left:1px solid rgba(34,36,38,.15);padding:0 0 0 2.5rem;margin:0 0 0 2.5rem}.ui.close.rail{width:calc(300px + 1em)}.ui.close.left.rail{padding:0 1em 0 0;margin:0 1em 0 0}.ui.close.right.rail{padding:0 0 0 1em;margin:0 0 0 1em}.ui.very.close.rail{width:calc(300px + .5em)}.ui.very.close.left.rail{padding:0 .5em 0 0;margin:0 .5em 0 0}.ui.very.close.right.rail{padding:0 0 0 .5em;margin:0 0 0 .5em}.ui.attached.left.rail,.ui.attached.right.rail{padding:0;margin:0}.ui.mini.rail{font-size:.78571429rem}.ui.tiny.rail{font-size:.85714286rem}.ui.small.rail{font-size:.92857143rem}.ui.rail{font-size:1rem}.ui.large.rail{font-size:1.14285714rem}.ui.big.rail{font-size:1.28571429rem}.ui.huge.rail{font-size:1.42857143rem}.ui.massive.rail{font-size:1.71428571rem}.ui.reveal{display:inherit;position:relative!important;font-size:0!important}.ui.reveal>.visible.content{position:absolute!important;top:0!important;left:0!important;z-index:3!important;-webkit-transition:all .5s ease .1s;transition:all .5s ease .1s}.ui.reveal>.hidden.content{position:relative!important;z-index:2!important}.ui.active.reveal .visible.content,.ui.reveal:hover .visible.content{z-index:4!important}.ui.slide.reveal{position:relative!important;overflow:hidden!important;white-space:nowrap}.ui.slide.reveal>.content{display:block;width:100%;float:left;margin:0;-webkit-transition:-webkit-transform .5s ease .1s;transition:-webkit-transform .5s ease .1s;transition:transform .5s ease .1s;transition:transform .5s ease .1s,-webkit-transform .5s ease .1s}.ui.slide.reveal>.visible.content{position:relative!important}.ui.slide.reveal>.hidden.content{position:absolute!important;left:0!important;width:100%!important;-webkit-transform:translateX(100%)!important;transform:translateX(100%)!important}.ui.slide.active.reveal>.visible.content,.ui.slide.reveal:hover>.visible.content{-webkit-transform:translateX(-100%)!important;transform:translateX(-100%)!important}.ui.slide.active.reveal>.hidden.content,.ui.slide.reveal:hover>.hidden.content,.ui.slide.right.reveal>.visible.content{-webkit-transform:translateX(0)!important;transform:translateX(0)!important}.ui.slide.right.reveal>.hidden.content{-webkit-transform:translateX(-100%)!important;transform:translateX(-100%)!important}.ui.slide.right.active.reveal>.visible.content,.ui.slide.right.reveal:hover>.visible.content{-webkit-transform:translateX(100%)!important;transform:translateX(100%)!important}.ui.slide.right.active.reveal>.hidden.content,.ui.slide.right.reveal:hover>.hidden.content{-webkit-transform:translateX(0)!important;transform:translateX(0)!important}.ui.slide.up.reveal>.hidden.content{-webkit-transform:translateY(100%)!important;transform:translateY(100%)!important}.ui.slide.up.active.reveal>.visible.content,.ui.slide.up.reveal:hover>.visible.content{-webkit-transform:translateY(-100%)!important;transform:translateY(-100%)!important}.ui.slide.up.active.reveal>.hidden.content,.ui.slide.up.reveal:hover>.hidden.content{-webkit-transform:translateY(0)!important;transform:translateY(0)!important}.ui.slide.down.reveal>.hidden.content{-webkit-transform:translateY(-100%)!important;transform:translateY(-100%)!important}.ui.slide.down.active.reveal>.visible.content,.ui.slide.down.reveal:hover>.visible.content{-webkit-transform:translateY(100%)!important;transform:translateY(100%)!important}.ui.slide.down.active.reveal>.hidden.content,.ui.slide.down.reveal:hover>.hidden.content{-webkit-transform:translateY(0)!important;transform:translateY(0)!important}.ui.fade.reveal>.visible.content{opacity:1}.ui.fade.active.reveal>.visible.content,.ui.fade.reveal:hover>.visible.content{opacity:0}.ui.move.reveal{position:relative!important;overflow:hidden!important;white-space:nowrap}.ui.move.reveal>.content{display:block;float:left;margin:0;-webkit-transition:-webkit-transform .5s cubic-bezier(.175,.885,.32,1) .1s;transition:-webkit-transform .5s cubic-bezier(.175,.885,.32,1) .1s;transition:transform .5s cubic-bezier(.175,.885,.32,1) .1s;transition:transform .5s cubic-bezier(.175,.885,.32,1) .1s,-webkit-transform .5s cubic-bezier(.175,.885,.32,1) .1s}.ui.move.reveal>.visible.content{position:relative!important}.ui.move.reveal>.hidden.content{position:absolute!important;left:0!important;width:100%!important}.ui.move.active.reveal>.visible.content,.ui.move.reveal:hover>.visible.content{-webkit-transform:translateX(-100%)!important;transform:translateX(-100%)!important}.ui.move.right.active.reveal>.visible.content,.ui.move.right.reveal:hover>.visible.content{-webkit-transform:translateX(100%)!important;transform:translateX(100%)!important}.ui.move.up.active.reveal>.visible.content,.ui.move.up.reveal:hover>.visible.content{-webkit-transform:translateY(-100%)!important;transform:translateY(-100%)!important}.ui.move.down.active.reveal>.visible.content,.ui.move.down.reveal:hover>.visible.content{-webkit-transform:translateY(100%)!important;transform:translateY(100%)!important}.ui.rotate.reveal>.visible.content{-webkit-transition-duration:.5s;transition-duration:.5s;-webkit-transform:rotate(0);transform:rotate(0)}.ui.rotate.reveal>.visible.content,.ui.rotate.right.reveal>.visible.content{-webkit-transform-origin:bottom right;transform-origin:bottom right}.ui.rotate.active.reveal>.visible.content,.ui.rotate.reveal:hover>.visible.content,.ui.rotate.right.active.reveal>.visible.content,.ui.rotate.right.reveal:hover>.visible.content{-webkit-transform:rotate(110deg);transform:rotate(110deg)}.ui.rotate.left.reveal>.visible.content{-webkit-transform-origin:bottom left;transform-origin:bottom left}.ui.rotate.left.active.reveal>.visible.content,.ui.rotate.left.reveal:hover>.visible.content{-webkit-transform:rotate(-110deg);transform:rotate(-110deg)}.ui.disabled.reveal:hover>.visible.visible.content{position:static!important;display:block!important;opacity:1!important;top:0!important;left:0!important;right:auto!important;bottom:auto!important;-webkit-transform:none!important;transform:none!important}.ui.disabled.reveal:hover>.hidden.hidden.content{display:none!important}.ui.visible.reveal{overflow:visible}.ui.instant.reveal>.content{-webkit-transition-delay:0s!important;transition-delay:0s!important}.ui.reveal>.content{font-size:1rem!important}.ui.segment{position:relative;background:#FFF;box-shadow:0 1px 2px 0 rgba(34,36,38,.15);margin:1rem 0;padding:1em;border-radius:.28571429rem;border:1px solid rgba(34,36,38,.15)}.ui.segment:first-child{margin-top:0}.ui.segment:last-child{margin-bottom:0}.ui.vertical.segment{margin:0;padding-left:0;padding-right:0;background:none;border-radius:0;box-shadow:none;border:none;border-bottom:1px solid rgba(34,36,38,.15)}.ui.vertical.segment:last-child{border-bottom:none}.ui.inverted.segment>.ui.header{color:#FFF}.ui[class*="bottom attached"].segment>[class*="top attached"].label{border-top-left-radius:0;border-top-right-radius:0}.ui[class*="top attached"].segment>[class*="bottom attached"].label{border-bottom-left-radius:0;border-bottom-right-radius:0}.ui.attached.segment:not(.top):not(.bottom)>[class*="top attached"].label{border-top-left-radius:0;border-top-right-radius:0}.ui.attached.segment:not(.top):not(.bottom)>[class*="bottom attached"].label{border-bottom-left-radius:0;border-bottom-right-radius:0}.ui.grid>.row>.ui.segment.column,.ui.grid>.ui.segment.column,.ui.page.grid.segment{padding-top:2em;padding-bottom:2em}.ui.grid.segment{margin:1rem 0;border-radius:.28571429rem}.ui.basic.table.segment{background:#FFF;border:1px solid rgba(34,36,38,.15);box-shadow:0 1px 2px 0 rgba(34,36,38,.15)}.ui[class*="very basic"].table.segment{padding:1em}.ui.piled.segment,.ui.piled.segments{margin:3em 0;box-shadow:'';z-index:auto}.ui.piled.segment:first-child{margin-top:0}.ui.piled.segment:last-child{margin-bottom:0}.ui.piled.segment:after,.ui.piled.segment:before,.ui.piled.segments:after,.ui.piled.segments:before{background-color:#FFF;visibility:visible;content:'';display:block;height:100%;left:0;position:absolute;width:100%;border:1px solid rgba(34,36,38,.15);box-shadow:''}.ui.piled.segment:before,.ui.piled.segments:before{-webkit-transform:rotate(-1.2deg);transform:rotate(-1.2deg);top:0;z-index:-2}.ui.piled.segment:after,.ui.piled.segments:after{-webkit-transform:rotate(1.2deg);transform:rotate(1.2deg);top:0;z-index:-1}.ui[class*="top attached"].piled.segment{margin-top:3em;margin-bottom:0}.ui.piled.segment[class*="top attached"]:first-child{margin-top:0}.ui.piled.segment[class*="bottom attached"]{margin-top:0;margin-bottom:3em}.ui.piled.segment[class*="bottom attached"]:last-child{margin-bottom:0}.ui.stacked.segment{padding-bottom:1.4em}.ui.stacked.segment:after,.ui.stacked.segment:before,.ui.stacked.segments:after,.ui.stacked.segments:before{content:'';position:absolute;bottom:-3px;left:0;border-top:1px solid rgba(34,36,38,.15);background:rgba(0,0,0,.03);width:100%;height:6px;visibility:visible}.ui.stacked.segment:before,.ui.stacked.segments:before{display:none}.ui.tall.stacked.segment:before,.ui.tall.stacked.segments:before{display:block;bottom:0}.ui.stacked.inverted.segment:after,.ui.stacked.inverted.segment:before,.ui.stacked.inverted.segments:after,.ui.stacked.inverted.segments:before{background-color:rgba(0,0,0,.03);border-top:1px solid rgba(34,36,38,.35)}.ui.padded.segment{padding:1.5em}.ui[class*="very padded"].segment{padding:3em}.ui.compact.segment{display:table}.ui.compact.segments{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex}.ui.compact.segments .segment,.ui.segments .compact.segment{display:block;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.ui.circular.segment{display:table-cell;padding:2em;text-align:center;vertical-align:middle;border-radius:500em}.ui.raised.segment,.ui.raised.segments{box-shadow:0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.15)}.ui.segments{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;position:relative;margin:1rem 0;border:1px solid rgba(34,36,38,.15);box-shadow:0 1px 2px 0 rgba(34,36,38,.15);border-radius:.28571429rem}.ui.segments:first-child{margin-top:0}.ui.segments:last-child{margin-bottom:0}.ui.segments>.segment{top:0;bottom:0;border-radius:0;margin:0;width:auto;box-shadow:none;border:none;border-top:1px solid rgba(34,36,38,.15)}.ui.segments:not(.horizontal)>.segment:first-child{border-top:none;margin-top:0;bottom:0;margin-bottom:0;top:0;border-radius:.28571429rem .28571429rem 0 0}.ui.segments:not(.horizontal)>.segment:last-child{top:0;bottom:0;margin-top:0;margin-bottom:0;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),none;border-radius:0 0 .28571429rem .28571429rem}.ui.segments:not(.horizontal)>.segment:only-child{border-radius:.28571429rem}.ui.segments>.ui.segments{border-top:1px solid rgba(34,36,38,.15);margin:1rem}.ui.segments>.segments:first-child{border-top:none}.ui.segments>.segment+.segments:not(.horizontal){margin-top:0}.ui.horizontal.segments{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;padding:0;background-color:#FFF;box-shadow:0 1px 2px 0 rgba(34,36,38,.15);margin:1rem 0;border-radius:.28571429rem;border:1px solid rgba(34,36,38,.15)}.ui.segments>.horizontal.segments{margin:0;background-color:transparent;border-radius:0;border:none;box-shadow:none;border-top:1px solid rgba(34,36,38,.15)}.ui.horizontal.segments>.segment{-webkit-box-flex:1;-webkit-flex:1 1 auto;flex:1 1 auto;-ms-flex:1 1 0px;margin:0;min-width:0;background-color:transparent;border-radius:0;border:none;box-shadow:none;border-left:1px solid rgba(34,36,38,.15)}.ui.segments>.horizontal.segments:first-child{border-top:none}.ui.horizontal.segments>.segment:first-child{border-left:none}.ui.disabled.segment{opacity:.45;color:rgba(40,40,40,.3)}.ui.loading.segment{position:relative;cursor:default;pointer-events:none;text-shadow:none!important;color:transparent!important;-webkit-transition:all 0s linear;transition:all 0s linear}.ui.loading.segment:before{position:absolute;content:'';top:0;left:0;background:rgba(255,255,255,.8);width:100%;height:100%;border-radius:.28571429rem;z-index:100}.ui.loading.segment:after{position:absolute;content:'';top:50%;left:50%;margin:-1.5em 0 0 -1.5em;width:3em;height:3em;-webkit-animation:segment-spin .6s linear;animation:segment-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 rgba(0,0,0,.1) rgba(0,0,0,.1);border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent;visibility:visible;z-index:101}@-webkit-keyframes segment-spin{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes segment-spin{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ui.basic.segment{background:none;box-shadow:none;border:none;border-radius:0}.ui.clearing.segment:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ui.red.segment:not(.inverted){border-top:2px solid #DB2828}.ui.inverted.red.segment{background-color:#DB2828!important;color:#FFF!important}.ui.orange.segment:not(.inverted){border-top:2px solid #F2711C}.ui.inverted.orange.segment{background-color:#F2711C!important;color:#FFF!important}.ui.yellow.segment:not(.inverted){border-top:2px solid #FBBD08}.ui.inverted.yellow.segment{background-color:#FBBD08!important;color:#FFF!important}.ui.olive.segment:not(.inverted){border-top:2px solid #B5CC18}.ui.inverted.olive.segment{background-color:#B5CC18!important;color:#FFF!important}.ui.green.segment:not(.inverted){border-top:2px solid #21BA45}.ui.inverted.green.segment{background-color:#21BA45!important;color:#FFF!important}.ui.teal.segment:not(.inverted){border-top:2px solid #00B5AD}.ui.inverted.teal.segment{background-color:#00B5AD!important;color:#FFF!important}.ui.blue.segment:not(.inverted){border-top:2px solid #2185D0}.ui.inverted.blue.segment{background-color:#2185D0!important;color:#FFF!important}.ui.violet.segment:not(.inverted){border-top:2px solid #6435C9}.ui.inverted.violet.segment{background-color:#6435C9!important;color:#FFF!important}.ui.purple.segment:not(.inverted){border-top:2px solid #A333C8}.ui.inverted.purple.segment{background-color:#A333C8!important;color:#FFF!important}.ui.pink.segment:not(.inverted){border-top:2px solid #E03997}.ui.inverted.pink.segment{background-color:#E03997!important;color:#FFF!important}.ui.brown.segment:not(.inverted){border-top:2px solid #A5673F}.ui.inverted.brown.segment{background-color:#A5673F!important;color:#FFF!important}.ui.grey.segment:not(.inverted){border-top:2px solid #767676}.ui.inverted.grey.segment{background-color:#767676!important;color:#FFF!important}.ui.black.segment:not(.inverted){border-top:2px solid #1B1C1D}.ui.inverted.black.segment{background-color:#1B1C1D!important;color:#FFF!important}.ui[class*="left aligned"].segment{text-align:left}.ui[class*="right aligned"].segment{text-align:right}.ui[class*="center aligned"].segment{text-align:center}.ui.floated.segment,.ui[class*="left floated"].segment{float:left;margin-right:1em}.ui[class*="right floated"].segment{float:right;margin-left:1em}.ui.inverted.segment{border:none;box-shadow:none}.ui.inverted.segment,.ui.primary.inverted.segment{background:#1B1C1D;color:rgba(255,255,255,.9)}.ui.inverted.segment .segment{color:rgba(0,0,0,.87)}.ui.inverted.segment .inverted.segment{color:rgba(255,255,255,.9)}.ui.inverted.attached.segment{border-color:#555}.ui.secondary.segment{background:#F3F4F5;color:rgba(0,0,0,.6)}.ui.secondary.inverted.segment{background:-webkit-linear-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.2) 100%) #4c4f52;background:linear-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.2) 100%) #4c4f52;color:rgba(255,255,255,.8)}.ui.tertiary.segment{background:#DCDDDE;color:rgba(0,0,0,.6)}.ui.tertiary.inverted.segment{background:-webkit-linear-gradient(rgba(255,255,255,.35) 0,rgba(255,255,255,.35) 100%) #717579;background:linear-gradient(rgba(255,255,255,.35) 0,rgba(255,255,255,.35) 100%) #717579;color:rgba(255,255,255,.8)}.ui.attached.segment{top:0;bottom:0;border-radius:0;margin:0 -1px;width:calc(100% + 2px);max-width:calc(100% + 2px);box-shadow:none;border:1px solid #D4D4D5}.ui.attached:not(.message)+.ui.attached.segment:not(.top){border-top:none}.ui[class*="top attached"].segment{bottom:0;margin-bottom:0;top:0;margin-top:1rem;border-radius:.28571429rem .28571429rem 0 0}.ui.segment[class*="top attached"]:first-child{margin-top:0}.ui.segment[class*="bottom attached"]{bottom:0;margin-top:0;top:0;margin-bottom:1rem;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),none;border-radius:0 0 .28571429rem .28571429rem}.ui.segment[class*="bottom attached"]:last-child{margin-bottom:0}.ui.mini.segment,.ui.mini.segments .segment{font-size:.78571429rem}.ui.tiny.segment,.ui.tiny.segments .segment{font-size:.85714286rem}.ui.small.segment,.ui.small.segments .segment{font-size:.92857143rem}.ui.segment,.ui.segments .segment{font-size:1rem}.ui.large.segment,.ui.large.segments .segment{font-size:1.14285714rem}.ui.big.segment,.ui.big.segments .segment{font-size:1.28571429rem}.ui.huge.segment,.ui.huge.segments .segment{font-size:1.42857143rem}.ui.massive.segment,.ui.massive.segments .segment{font-size:1.71428571rem}.ui.steps{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;margin:1em 0;background:0 0;box-shadow:none;line-height:1.14285714em;border-radius:.28571429rem;border:1px solid rgba(34,36,38,.15)}.ui.steps:first-child{margin-top:0}.ui.steps:last-child{margin-bottom:0}.ui.steps .step{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;vertical-align:middle;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;margin:0;padding:1.14285714em 2em;background:#FFF;color:rgba(0,0,0,.87);box-shadow:none;border-radius:0;border:none;border-right:1px solid rgba(34,36,38,.15);-webkit-transition:background-color .1s ease,opacity .1s ease,color .1s ease,box-shadow .1s ease;transition:background-color .1s ease,opacity .1s ease,color .1s ease,box-shadow .1s ease}.ui.steps .step:after{position:absolute;z-index:2;content:'';top:50%;right:0;border:solid;background-color:#FFF;width:1.14285714em;height:1.14285714em;border-color:rgba(34,36,38,.15);border-width:0 1px 1px 0;-webkit-transition:background-color .1s ease,opacity .1s ease,color .1s ease,box-shadow .1s ease;transition:background-color .1s ease,opacity .1s ease,color .1s ease,box-shadow .1s ease;-webkit-transform:translateY(-50%) translateX(50%) rotate(-45deg);transform:translateY(-50%) translateX(50%) rotate(-45deg)}.ui.steps .step:first-child{padding-left:2em;border-radius:.28571429rem 0 0 .28571429rem}.ui.steps .step:last-child{border-radius:0 .28571429rem .28571429rem 0;border-right:none;margin-right:0}.ui.steps .step:only-child{border-radius:.28571429rem}.ui.steps .step .title{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:1.14285714em;font-weight:700}.ui.steps .step>.title{width:100%}.ui.steps .step .description{font-weight:400;font-size:.92857143em;color:rgba(0,0,0,.87)}.ui.steps .step>.description{width:100%}.ui.steps .step .title~.description{margin-top:.25em}.ui.steps .step>.icon{line-height:1;font-size:2.5em;margin:0 1rem 0 0}.ui.steps .step>.icon,.ui.steps .step>.icon~.content{display:block;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;-webkit-align-self:middle;-ms-flex-item-align:middle;align-self:middle}.ui.steps .step>.icon~.content{-webkit-box-flex:1 0 auto;-webkit-flex-grow:1 0 auto;-ms-flex-positive:1 0 auto;flex-grow:1 0 auto}.ui.steps:not(.vertical) .step>.icon{width:auto}.ui.steps .link.step,.ui.steps a.step{cursor:pointer}.ui.ordered.steps{counter-reset:ordered}.ui.ordered.steps .step:before{display:block;position:static;text-align:center;content:counters(ordered,".");-webkit-align-self:middle;-ms-flex-item-align:middle;align-self:middle;margin-right:1rem;font-size:2.5em;counter-increment:ordered;font-family:inherit;font-weight:700}.ui.ordered.steps .step>*{display:block;-webkit-align-self:middle;-ms-flex-item-align:middle;align-self:middle}.ui.vertical.steps{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow:visible}.ui.vertical.steps .step{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;border-radius:0;padding:1.14285714em 2em;border-right:none;border-bottom:1px solid rgba(34,36,38,.15)}.ui.vertical.steps .step:first-child{padding:1.14285714em 2em;border-radius:.28571429rem .28571429rem 0 0}.ui.vertical.steps .step:last-child{border-bottom:none;border-radius:0 0 .28571429rem .28571429rem}.ui.vertical.steps .step:only-child{border-radius:.28571429rem}.ui.vertical.steps .step:after{top:50%;right:0;border-width:0 1px 1px 0;display:none}.ui.vertical.steps .active.step:after{display:block}.ui.vertical.steps .step:last-child:after{display:none}.ui.vertical.steps .active.step:last-child:after{display:block}@media only screen and (max-width:767px){.ui.steps{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;overflow:visible;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.ui.steps .step{width:100%!important;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;border-radius:0;padding:1.14285714em 2em}.ui.steps .step:first-child{padding:1.14285714em 2em;border-radius:.28571429rem .28571429rem 0 0}.ui.steps .step:last-child{border-radius:0 0 .28571429rem .28571429rem}.ui.steps .step:after{display:none!important}.ui.steps .step .content{text-align:center}.ui.ordered.steps .step:before,.ui.steps .step>.icon{margin:0 0 1rem}}.ui.steps .link.step:hover,.ui.steps .link.step:hover::after,.ui.steps a.step:hover,.ui.steps a.step:hover::after{background:#F9FAFB;color:rgba(0,0,0,.8)}.ui.steps .link.step:active,.ui.steps .link.step:active::after,.ui.steps a.step:active,.ui.steps a.step:active::after{background:#F3F4F5;color:rgba(0,0,0,.9)}.ui.steps .step.active{cursor:auto;background:#F3F4F5}.ui.steps .step.active:after{background:#F3F4F5}.ui.steps .step.active .title{color:#4183C4}.ui.ordered.steps .step.active:before,.ui.steps .active.step .icon{color:rgba(0,0,0,.85)}.ui.steps .active.step:after,.ui.steps .step:after{display:block}.ui.steps .active.step:last-child:after,.ui.steps .step:last-child:after{display:none}.ui.steps .link.active.step:hover,.ui.steps .link.active.step:hover::after,.ui.steps a.active.step:hover,.ui.steps a.active.step:hover::after{cursor:pointer;background:#DCDDDE;color:rgba(0,0,0,.87)}.ui.ordered.steps .step.completed:before,.ui.steps .step.completed>.icon:before{color:#21BA45;font-family:Step;content:'\e800'}.ui.steps .disabled.step{cursor:auto;background:#FFF;pointer-events:none}.ui.steps .disabled.step,.ui.steps .disabled.step .description,.ui.steps .disabled.step .title{color:rgba(40,40,40,.3)}.ui.steps .disabled.step:after{background:#FFF}@media only screen and (max-width:991px){.ui[class*="tablet stackable"].steps{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;overflow:visible;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.ui[class*="tablet stackable"].steps .step{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;border-radius:0;padding:1.14285714em 2em}.ui[class*="tablet stackable"].steps .step:first-child{padding:1.14285714em 2em;border-radius:.28571429rem .28571429rem 0 0}.ui[class*="tablet stackable"].steps .step:last-child{border-radius:0 0 .28571429rem .28571429rem}.ui[class*="tablet stackable"].steps .step:after{display:none!important}.ui[class*="tablet stackable"].steps .step .content{text-align:center}.ui[class*="tablet stackable"].ordered.steps .step:before,.ui[class*="tablet stackable"].steps .step>.icon{margin:0 0 1rem}}.ui.fluid.steps{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%}.ui.attached.steps{width:calc(100% + 2px)!important;margin:0 -1px;max-width:calc(100% + 2px);border-radius:.28571429rem .28571429rem 0 0}.ui.attached.steps .step:first-child{border-radius:.28571429rem 0 0}.ui.attached.steps .step:last-child{border-radius:0 .28571429rem 0 0}.ui.bottom.attached.steps{margin:0 -1px;border-radius:0 0 .28571429rem .28571429rem}.ui.bottom.attached.steps .step:first-child{border-radius:0 0 0 .28571429rem}.ui.bottom.attached.steps .step:last-child{border-radius:0 0 .28571429rem}.ui.eight.steps,.ui.five.steps,.ui.four.steps,.ui.one.steps,.ui.seven.steps,.ui.six.steps,.ui.three.steps,.ui.two.steps{width:100%}.ui.eight.steps>.step,.ui.five.steps>.step,.ui.four.steps>.step,.ui.one.steps>.step,.ui.seven.steps>.step,.ui.six.steps>.step,.ui.three.steps>.step,.ui.two.steps>.step{-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.ui.one.steps>.step{width:100%}.ui.two.steps>.step{width:50%}.ui.three.steps>.step{width:33.333%}.ui.four.steps>.step{width:25%}.ui.five.steps>.step{width:20%}.ui.six.steps>.step{width:16.666%}.ui.seven.steps>.step{width:14.285%}.ui.eight.steps>.step{width:12.5%}.ui.mini.step,.ui.mini.steps .step{font-size:.78571429rem}.ui.tiny.step,.ui.tiny.steps .step{font-size:.85714286rem}.ui.small.step,.ui.small.steps .step{font-size:.92857143rem}.ui.step,.ui.steps .step{font-size:1rem}.ui.large.step,.ui.large.steps .step{font-size:1.14285714rem}.ui.big.step,.ui.big.steps .step{font-size:1.28571429rem}.ui.huge.step,.ui.huge.steps .step{font-size:1.42857143rem}.ui.massive.step,.ui.massive.steps .step{font-size:1.71428571rem}@font-face{font-family:Step;src:url(data:application/x-font-ttf;charset=utf-8;;base64,AAEAAAAOAIAAAwBgT1MvMj3hSQEAAADsAAAAVmNtYXDQEhm3AAABRAAAAUpjdnQgBkn/lAAABuwAAAAcZnBnbYoKeDsAAAcIAAAJkWdhc3AAAAAQAAAG5AAAAAhnbHlm32cEdgAAApAAAAC2aGVhZAErPHsAAANIAAAANmhoZWEHUwNNAAADgAAAACRobXR4CykAAAAAA6QAAAAMbG9jYQA4AFsAAAOwAAAACG1heHAApgm8AAADuAAAACBuYW1lzJ0aHAAAA9gAAALNcG9zdK69QJgAAAaoAAAAO3ByZXCSoZr/AAAQnAAAAFYAAQO4AZAABQAIAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA6ADoAQNS/2oAWgMLAE8AAAABAAAAAAAAAAAAAwAAAAMAAAAcAAEAAAAAAEQAAwABAAAAHAAEACgAAAAGAAQAAQACAADoAf//AAAAAOgA//8AABgBAAEAAAAAAAAAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAADpAKYABUAHEAZDwEAAQFCAAIBAmoAAQABagAAAGEUFxQDEisBFAcBBiInASY0PwE2Mh8BATYyHwEWA6QP/iAQLBD+6g8PTBAsEKQBbhAsEEwPAhYWEP4gDw8BFhAsEEwQEKUBbxAQTBAAAAH//f+xA18DCwAMABJADwABAQpDAAAACwBEFRMCESsBFA4BIi4CPgEyHgEDWXLG6MhuBnq89Lp+AV51xHR0xOrEdHTEAAAAAAEAAAABAADDeRpdXw889QALA+gAAAAAzzWYjQAAAADPNWBN//3/sQOkAwsAAAAIAAIAAAAAAAAAAQAAA1L/agBaA+gAAP/3A6QAAQAAAAAAAAAAAAAAAAAAAAMD6AAAA+gAAANZAAAAAAAAADgAWwABAAAAAwAWAAEAAAAAAAIABgATAG4AAAAtCZEAAAAAAAAAEgDeAAEAAAAAAAAANQAAAAEAAAAAAAEACAA1AAEAAAAAAAIABwA9AAEAAAAAAAMACABEAAEAAAAAAAQACABMAAEAAAAAAAUACwBUAAEAAAAAAAYACABfAAEAAAAAAAoAKwBnAAEAAAAAAAsAEwCSAAMAAQQJAAAAagClAAMAAQQJAAEAEAEPAAMAAQQJAAIADgEfAAMAAQQJAAMAEAEtAAMAAQQJAAQAEAE9AAMAAQQJAAUAFgFNAAMAAQQJAAYAEAFjAAMAAQQJAAoAVgFzAAMAAQQJAAsAJgHJQ29weXJpZ2h0IChDKSAyMDE0IGJ5IG9yaWdpbmFsIGF1dGhvcnMgQCBmb250ZWxsby5jb21mb250ZWxsb1JlZ3VsYXJmb250ZWxsb2ZvbnRlbGxvVmVyc2lvbiAxLjBmb250ZWxsb0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABDACkAIAAyADAAMQA0ACAAYgB5ACAAbwByAGkAZwBpAG4AYQBsACAAYQB1AHQAaABvAHIAcwAgAEAAIABmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQBmAG8AbgB0AGUAbABsAG8AUgBlAGcAdQBsAGEAcgBmAG8AbgB0AGUAbABsAG8AZgBvAG4AdABlAGwAbABvAFYAZQByAHMAaQBvAG4AIAAxAC4AMABmAG8AbgB0AGUAbABsAG8ARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAQIBAwljaGVja21hcmsGY2lyY2xlAAAAAAEAAf//AA8AAAAAAAAAAAAAAAAAAAAAADIAMgML/7EDC/+xsAAssCBgZi2wASwgZCCwwFCwBCZasARFW1ghIyEbilggsFBQWCGwQFkbILA4UFghsDhZWSCwCkVhZLAoUFghsApFILAwUFghsDBZGyCwwFBYIGYgiophILAKUFhgGyCwIFBYIbAKYBsgsDZQWCGwNmAbYFlZWRuwACtZWSOwAFBYZVlZLbACLCBFILAEJWFkILAFQ1BYsAUjQrAGI0IbISFZsAFgLbADLCMhIyEgZLEFYkIgsAYjQrIKAAIqISCwBkMgiiCKsAArsTAFJYpRWGBQG2FSWVgjWSEgsEBTWLAAKxshsEBZI7AAUFhlWS2wBCywB0MrsgACAENgQi2wBSywByNCIyCwACNCYbCAYrABYLAEKi2wBiwgIEUgsAJFY7ABRWJgRLABYC2wBywgIEUgsAArI7ECBCVgIEWKI2EgZCCwIFBYIbAAG7AwUFiwIBuwQFlZI7AAUFhlWbADJSNhRESwAWAtsAgssQUFRbABYUQtsAkssAFgICCwCUNKsABQWCCwCSNCWbAKQ0qwAFJYILAKI0JZLbAKLCC4BABiILgEAGOKI2GwC0NgIIpgILALI0IjLbALLEtUWLEHAURZJLANZSN4LbAMLEtRWEtTWLEHAURZGyFZJLATZSN4LbANLLEADENVWLEMDEOwAWFCsAorWbAAQ7ACJUKxCQIlQrEKAiVCsAEWIyCwAyVQWLEBAENgsAQlQoqKIIojYbAJKiEjsAFhIIojYbAJKiEbsQEAQ2CwAiVCsAIlYbAJKiFZsAlDR7AKQ0dgsIBiILACRWOwAUViYLEAABMjRLABQ7AAPrIBAQFDYEItsA4ssQAFRVRYALAMI0IgYLABYbUNDQEACwBCQopgsQ0FK7BtKxsiWS2wDyyxAA4rLbAQLLEBDistsBEssQIOKy2wEiyxAw4rLbATLLEEDistsBQssQUOKy2wFSyxBg4rLbAWLLEHDistsBcssQgOKy2wGCyxCQ4rLbAZLLAIK7EABUVUWACwDCNCIGCwAWG1DQ0BAAsAQkKKYLENBSuwbSsbIlktsBossQAZKy2wGyyxARkrLbAcLLECGSstsB0ssQMZKy2wHiyxBBkrLbAfLLEFGSstsCAssQYZKy2wISyxBxkrLbAiLLEIGSstsCMssQkZKy2wJCwgPLABYC2wJSwgYLANYCBDI7ABYEOwAiVhsAFgsCQqIS2wJiywJSuwJSotsCcsICBHICCwAkVjsAFFYmAjYTgjIIpVWCBHICCwAkVjsAFFYmAjYTgbIVktsCgssQAFRVRYALABFrAnKrABFTAbIlktsCkssAgrsQAFRVRYALABFrAnKrABFTAbIlktsCosIDWwAWAtsCssALADRWOwAUVisAArsAJFY7ABRWKwACuwABa0AAAAAABEPiM4sSoBFSotsCwsIDwgRyCwAkVjsAFFYmCwAENhOC2wLSwuFzwtsC4sIDwgRyCwAkVjsAFFYmCwAENhsAFDYzgtsC8ssQIAFiUgLiBHsAAjQrACJUmKikcjRyNhIFhiGyFZsAEjQrIuAQEVFCotsDAssAAWsAQlsAQlRyNHI2GwBkUrZYouIyAgPIo4LbAxLLAAFrAEJbAEJSAuRyNHI2EgsAQjQrAGRSsgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjILAIQyCKI0cjRyNhI0ZgsARDsIBiYCCwACsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsIBiYSMgILAEJiNGYTgbI7AIQ0awAiWwCENHI0cjYWAgsARDsIBiYCMgsAArI7AEQ2CwACuwBSVhsAUlsIBisAQmYSCwBCVgZCOwAyVgZFBYIRsjIVkjICCwBCYjRmE4WS2wMiywABYgICCwBSYgLkcjRyNhIzw4LbAzLLAAFiCwCCNCICAgRiNHsAArI2E4LbA0LLAAFrADJbACJUcjRyNhsABUWC4gPCMhG7ACJbACJUcjRyNhILAFJbAEJUcjRyNhsAYlsAUlSbACJWGwAUVjIyBYYhshWWOwAUViYCMuIyAgPIo4IyFZLbA1LLAAFiCwCEMgLkcjRyNhIGCwIGBmsIBiIyAgPIo4LbA2LCMgLkawAiVGUlggPFkusSYBFCstsDcsIyAuRrACJUZQWCA8WS6xJgEUKy2wOCwjIC5GsAIlRlJYIDxZIyAuRrACJUZQWCA8WS6xJgEUKy2wOSywMCsjIC5GsAIlRlJYIDxZLrEmARQrLbA6LLAxK4ogIDywBCNCijgjIC5GsAIlRlJYIDxZLrEmARQrsARDLrAmKy2wOyywABawBCWwBCYgLkcjRyNhsAZFKyMgPCAuIzixJgEUKy2wPCyxCAQlQrAAFrAEJbAEJSAuRyNHI2EgsAQjQrAGRSsgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwgGJgILAAKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwgGJhsAIlRmE4IyA8IzgbISAgRiNHsAArI2E4IVmxJgEUKy2wPSywMCsusSYBFCstsD4ssDErISMgIDywBCNCIzixJgEUK7AEQy6wJistsD8ssAAVIEewACNCsgABARUUEy6wLCotsEAssAAVIEewACNCsgABARUUEy6wLCotsEEssQABFBOwLSotsEIssC8qLbBDLLAAFkUjIC4gRoojYTixJgEUKy2wRCywCCNCsEMrLbBFLLIAADwrLbBGLLIAATwrLbBHLLIBADwrLbBILLIBATwrLbBJLLIAAD0rLbBKLLIAAT0rLbBLLLIBAD0rLbBMLLIBAT0rLbBNLLIAADkrLbBOLLIAATkrLbBPLLIBADkrLbBQLLIBATkrLbBRLLIAADsrLbBSLLIAATsrLbBTLLIBADsrLbBULLIBATsrLbBVLLIAAD4rLbBWLLIAAT4rLbBXLLIBAD4rLbBYLLIBAT4rLbBZLLIAADorLbBaLLIAATorLbBbLLIBADorLbBcLLIBATorLbBdLLAyKy6xJgEUKy2wXiywMiuwNistsF8ssDIrsDcrLbBgLLAAFrAyK7A4Ky2wYSywMysusSYBFCstsGIssDMrsDYrLbBjLLAzK7A3Ky2wZCywMyuwOCstsGUssDQrLrEmARQrLbBmLLA0K7A2Ky2wZyywNCuwNystsGgssDQrsDgrLbBpLLA1Ky6xJgEUKy2waiywNSuwNistsGsssDUrsDcrLbBsLLA1K7A4Ky2wbSwrsAhlsAMkUHiwARUwLQAAAEu4AMhSWLEBAY5ZuQgACABjILABI0SwAyNwsgQoCUVSRLIKAgcqsQYBRLEkAYhRWLBAiFixBgNEsSYBiFFYuAQAiFixBgFEWVlZWbgB/4WwBI2xBQBEAAA=) format('truetype'),url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAoUAA4AAAAAEPQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEQAAABWPeFJAWNtYXAAAAGIAAAAOgAAAUrQEhm3Y3Z0IAAAAcQAAAAUAAAAHAZJ/5RmcGdtAAAB2AAABPkAAAmRigp4O2dhc3AAAAbUAAAACAAAAAgAAAAQZ2x5ZgAABtwAAACuAAAAtt9nBHZoZWFkAAAHjAAAADUAAAA2ASs8e2hoZWEAAAfEAAAAIAAAACQHUwNNaG10eAAAB+QAAAAMAAAADAspAABsb2NhAAAH8AAAAAgAAAAIADgAW21heHAAAAf4AAAAIAAAACAApgm8bmFtZQAACBgAAAF3AAACzcydGhxwb3N0AAAJkAAAACoAAAA7rr1AmHByZXAAAAm8AAAAVgAAAFaSoZr/eJxjYGTewTiBgZWBg6mKaQ8DA0MPhGZ8wGDIyMTAwMTAysyAFQSkuaYwOLxgeMHIHPQ/iyGKmZvBHyjMCJIDAPe9C2B4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGF4w/v8PUvCCAURLMELVAwEjG8OIBwBk5AavAAB4nGNgQANGDEbM3P83gjAAELQD4XicnVXZdtNWFJU8ZHASOmSgoA7X3DhQ68qEKRgwaSrFdiEdHAitBB2kDHTkncc+62uOQrtWH/m07n09JLR0rbYsls++R1tn2DrnRhwjKn0aiGvUoZKXA6msPZZK90lc13Uvj5UMBnFdthJPSZuonSRKat3sUC7xWOsqWSdYJ+PlIFZPVZ5noAziFB5lSUQbRBuplyZJ4onjJ4kWZxAfJUkgJaMQp9LIUEI1GsRS1aFM6dCr1xNx00DKRqMedVhU90PFJ8c1p9SsA0YqVznCFevVRr4bpwMve5DEOsGzrYcxHnisfpQqkIqR6cg/dkpOlIaBVHHUoVbi6DCTX/eRTCrNQKaMYkWl7oG43f102xYxPXQ6vi5KlUaqurnOKJrt0fGogygP2cbppNzQ2fbw5RlTVKtdcbPtQGYNXErJbHSfRAAdJlLj6QFONZwCqRn1R8XZ588BEslclKo8VTKHegOZMzt7cTHtbiersnCknwcyb3Z2452HQ6dXh3/R+hdM4cxHj+Jifj5C+lBqfiJOJKVGWMzyp4YfcVcgQrkxiAsXyuBThDl0RdrZZl3jtTH2hs/5SqlhPQna6KP4fgr9TiQrHGdRo/VInM1j13Wt3GdQS7W7Fzsyr0OVIu7vCwuuM+eEYZ4WC1VfnvneBTT/Bohn/EDeNIVL+5YpSrRvm6JMu2iKCu0SVKVdNsUU7YoppmnPmmKG9h1TzNKeMzLj/8vc55H7HN7xkJv2XeSmfQ+5ad9HbtoPkJtWITdtHblpLyA3rUZu2lWjOnYEGgZpF1IVQdA0svph3Fab9UDWjDR8aWDyLmLI+upER521tcofxX914gsHcmmip7siF5viLq/bFj483e6rj5pG3bDV+MaR8jAeRnocmtBZ+c3hv+1N3S6a7jKqMugBFUwKwABl7UAC0zrbCaT1mqf48gdgXIZ4zkpDtVSfO4am7+V5X/exOfG+x+3GLrdcd3kJWdYNcmP28N9SZKrrH+UtrVQnR6wrJ49VaxhDKrwour6SlHu0tRu/KKmy8l6U1srnk5CbPYMbQlu27mGwI0xpyiUeXlOlKD3UUo6yQyxvKco84JSLC1qGxLgOdQ9qa8TpoXoYGwshhqG0vRBwSCldFd+0ynfxHqtr2Oj4xRXh6XpyEhGf4ir7UfBU10b96A7avGbdMoMpVaqn+4xPsa/b9lFZaaSOsxe3VAfXNOsaORXTT+Rr4HRvOGjdAz1UfDRBI1U1x+jGKGM0ljXl3wR0MVZ+w2jVYvs93E+dpFWsuUuY7JsT9+C0u/0q+7WcW0bW/dcGvW3kip8jMb8tCvw7B2K3ZA3UO5OBGAvIWdAYxhYmdxiug23EbfY/Jqf/34aFRXJXOxq7eerD1ZNRJXfZ8rjLTXZZ16M2R9VOGvsIjS0PN+bY4XIstsRgQbb+wf8x7gF3aVEC4NDIZZiI2nShnurh6h6rsW04VxIBds2x43QAegAuQd8cu9bzCYD13CPnLsB9cgh2yCH4lByCz8i5BfA5OQRfkEMwIIdgl5w7AA/IIXhIDsEeOQSPyNkE+JIcgq/IIYjJIUjIuQ3wmByCJ+QQfE0OwTdGrk5k/pYH2QD6zqKbQKmdGhzaOGRGrk3Y+zxY9oFFZB9aROqRkesT6lMeLPV7i0j9wSJSfzRyY0L9iQdL/dkiUn+xiNRnxpeZIymvDp7zjg7+BJfqrV4AAAAAAQAB//8AD3icY2BkAALmJUwzGEQZZBwk+RkZGBmdGJgYmbIYgMwsoGSiiLgIs5A2owg7I5uSOqOaiT2jmZE8I5gQY17C/09BQEfg3yt+fh8gvYQxD0j68DOJiQn8U+DnZxQDcQUEljLmCwBpBgbG/3//b2SOZ+Zm4GEQcuAH2sblDLSEm8FFVJhJEGgLH6OSHpMdo5EcI3Nk0bEXJ/LYqvZ82VXHGFd6pKTkyCsQwQAAq+QkqAAAeJxjYGRgYADiw5VSsfH8Nl8ZuJlfAEUYzpvO6IXQCb7///7fyLyEmRvI5WBgAokCAFb/DJAAAAB4nGNgZGBgDvqfxRDF/IKB4f935iUMQBEUwAwAi5YFpgPoAAAD6AAAA1kAAAAAAAAAOABbAAEAAAADABYAAQAAAAAAAgAGABMAbgAAAC0JkQAAAAB4nHWQy2rCQBSG//HSi0JbWui2sypKabxgN4IgWHTTbqS4LTHGJBIzMhkFX6Pv0IfpS/RZ+puMpShNmMx3vjlz5mQAXOMbAvnzxJGzwBmjnAs4Rc9ykf7Zcon8YrmMKt4sn9C/W67gAYHlKm7wwQqidM5ogU/LAlfi0nIBF+LOcpH+0XKJ3LNcxq14tXxC71muYCJSy1Xci6+BWm11FIRG1gZ12W62OnK6lYoqStxYumsTKp3KvpyrxPhxrBxPLfc89oN17Op9uJ8nvk4jlciW09yrkZ/42jX+bFc93QRtY+ZyrtVSDm2GXGm18D3jhMasuo3G3/MwgMIKW2hEvKoQBhI12jrnNppooUOaMkMyM8+KkMBFTONizR1htpIy7nPMGSW0PjNisgOP3+WRH5MC7o9ZRR+tHsYT0u6MKPOSfTns7jBrREqyTDezs9/eU2x4WpvWcNeuS511JTE8qCF5H7u1BY1H72S3Ymi7aPD95/9+AN1fhEsAeJxjYGKAAC4G7ICZgYGRiZGZMzkjNTk7N7Eomy05syg5J5WBAQBE1QZBAABLuADIUlixAQGOWbkIAAgAYyCwASNEsAMjcLIEKAlFUkSyCgIHKrEGAUSxJAGIUViwQIhYsQYDRLEmAYhRWLgEAIhYsQYBRFlZWVm4Af+FsASNsQUARAAA) format('woff')}.ui.breadcrumb{line-height:1;display:inline-block;margin:0;vertical-align:middle}.ui.breadcrumb:first-child{margin-top:0}.ui.breadcrumb:last-child{margin-bottom:0}.ui.breadcrumb .divider{display:inline-block;opacity:.7;margin:0 .21428571rem;font-size:.92857143em;color:rgba(0,0,0,.4);vertical-align:baseline}.ui.breadcrumb a{color:#4183C4}.ui.breadcrumb a:hover{color:#1e70bf}.ui.breadcrumb .icon.divider{font-size:.85714286em;vertical-align:baseline}.ui.breadcrumb a.section{cursor:pointer}.ui.breadcrumb .section{display:inline-block;margin:0;padding:0}.ui.breadcrumb.segment{display:inline-block;padding:.78571429em 1em}.ui.breadcrumb .active.section{font-weight:700}.ui.mini.breadcrumb{font-size:.78571429rem}.ui.tiny.breadcrumb{font-size:.85714286rem}.ui.small.breadcrumb{font-size:.92857143rem}.ui.breadcrumb{font-size:1rem}.ui.large.breadcrumb{font-size:1.14285714rem}.ui.big.breadcrumb{font-size:1.28571429rem}.ui.huge.breadcrumb{font-size:1.42857143rem}.ui.massive.breadcrumb{font-size:1.71428571rem}.ui.form{position:relative;max-width:100%}.ui.form>p{margin:1em 0}.ui.form .field{clear:both;margin:0 0 1em}.ui.form .field:last-child,.ui.form .fields:last-child .field{margin-bottom:0}.ui.form .fields .field{clear:both;margin:0}.ui.form .field>label{display:block;margin:0 0 .28571429rem;color:rgba(0,0,0,.87);font-size:.92857143em;font-weight:700;text-transform:none}.ui.form input:not([type]),.ui.form input[type=text],.ui.form input[type=email],.ui.form input[type=search],.ui.form input[type=password],.ui.form input[type=date],.ui.form input[type=datetime-local],.ui.form input[type=tel],.ui.form input[type=time],.ui.form input[type=file],.ui.form input[type=url],.ui.form input[type=number],.ui.form textarea{width:100%;vertical-align:top}.ui.form ::-webkit-datetime-edit,.ui.form ::-webkit-inner-spin-button{height:1.2142em}.ui.form input:not([type]),.ui.form input[type=text],.ui.form input[type=email],.ui.form input[type=search],.ui.form input[type=password],.ui.form input[type=date],.ui.form input[type=datetime-local],.ui.form input[type=tel],.ui.form input[type=time],.ui.form input[type=file],.ui.form input[type=url],.ui.form input[type=number]{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;margin:0;outline:0;-webkit-appearance:none;tap-highlight-color:rgba(255,255,255,0);line-height:1.2142em;padding:.67861429em 1em;font-size:1em;background:#FFF;border:1px solid rgba(34,36,38,.15);color:rgba(0,0,0,.87);border-radius:.28571429rem;box-shadow:0 0 0 0 transparent inset;-webkit-transition:color .1s ease,border-color .1s ease;transition:color .1s ease,border-color .1s ease}.ui.form textarea{margin:0;-webkit-appearance:none;tap-highlight-color:rgba(255,255,255,0);padding:.78571429em 1em;background:#FFF;border:1px solid rgba(34,36,38,.15);outline:0;color:rgba(0,0,0,.87);border-radius:.28571429rem;box-shadow:0 0 0 0 transparent inset;-webkit-transition:color .1s ease,border-color .1s ease;transition:color .1s ease,border-color .1s ease;font-size:1em;line-height:1.2857;resize:vertical}.ui.form textarea:not([rows]){height:12em;min-height:8em;max-height:24em}.ui.form input[type=checkbox],.ui.form textarea{vertical-align:top}.ui.form input.attached{width:auto}.ui.form select{display:block;height:auto;width:100%;background:#FFF;border:1px solid rgba(34,36,38,.15);border-radius:.28571429rem;box-shadow:0 0 0 0 transparent inset;padding:.62em 1em;color:rgba(0,0,0,.87);-webkit-transition:color .1s ease,border-color .1s ease;transition:color .1s ease,border-color .1s ease}.ui.form .field>.selection.dropdown{width:100%}.ui.form .field>.selection.dropdown>.dropdown.icon{float:right}.ui.form .inline.field>.selection.dropdown,.ui.form .inline.fields .field>.selection.dropdown{width:auto}.ui.form .inline.field>.selection.dropdown>.dropdown.icon,.ui.form .inline.fields .field>.selection.dropdown>.dropdown.icon{float:none}.ui.form .field .ui.input,.ui.form .fields .field .ui.input,.ui.form .wide.field .ui.input{width:100%}.ui.form .inline.field:not(.wide) .ui.input,.ui.form .inline.fields .field:not(.wide) .ui.input{width:auto;vertical-align:middle}.ui.form .field .ui.input input,.ui.form .fields .field .ui.input input{width:auto}.ui.form .eight.fields .ui.input input,.ui.form .five.fields .ui.input input,.ui.form .four.fields .ui.input input,.ui.form .nine.fields .ui.input input,.ui.form .seven.fields .ui.input input,.ui.form .six.fields .ui.input input,.ui.form .ten.fields .ui.input input,.ui.form .three.fields .ui.input input,.ui.form .two.fields .ui.input input,.ui.form .wide.field .ui.input input{-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;width:0}.ui.form .error.message,.ui.form .success.message,.ui.form .warning.message{display:none}.ui.form .message:first-child{margin-top:0}.ui.form .field .prompt.label{white-space:normal;background:#FFF!important;border:1px solid #E0B4B4!important;color:#9F3A38!important}.ui.form .inline.field .prompt,.ui.form .inline.fields .field .prompt{vertical-align:top;margin:-.25em 0 -.5em .5em}.ui.form .inline.field .prompt:before,.ui.form .inline.fields .field .prompt:before{border-width:0 0 1px 1px;bottom:auto;right:auto;top:50%;left:0}.ui.form .field.field input:-webkit-autofill{box-shadow:0 0 0 100px ivory inset!important;border-color:#E5DFA1!important}.ui.form .field.field input:-webkit-autofill:focus{box-shadow:0 0 0 100px ivory inset!important;border-color:#D5C315!important}.ui.form .error.error input:-webkit-autofill{box-shadow:0 0 0 100px #FFFAF0 inset!important;border-color:#E0B4B4!important}.ui.form ::-webkit-input-placeholder{color:rgba(191,191,191,.87)}.ui.form :-ms-input-placeholder{color:rgba(191,191,191,.87)}.ui.form ::-moz-placeholder{color:rgba(191,191,191,.87)}.ui.form :focus::-webkit-input-placeholder{color:rgba(115,115,115,.87)}.ui.form :focus:-ms-input-placeholder{color:rgba(115,115,115,.87)}.ui.form :focus::-moz-placeholder{color:rgba(115,115,115,.87)}.ui.form .error ::-webkit-input-placeholder{color:#e7bdbc}.ui.form .error :-ms-input-placeholder{color:#e7bdbc!important}.ui.form .error ::-moz-placeholder{color:#e7bdbc}.ui.form .error :focus::-webkit-input-placeholder{color:#da9796}.ui.form .error :focus:-ms-input-placeholder{color:#da9796!important}.ui.form .error :focus::-moz-placeholder{color:#da9796}.ui.form input:not([type]):focus,.ui.form input[type=text]:focus,.ui.form input[type=email]:focus,.ui.form input[type=search]:focus,.ui.form input[type=password]:focus,.ui.form input[type=date]:focus,.ui.form input[type=datetime-local]:focus,.ui.form input[type=tel]:focus,.ui.form input[type=time]:focus,.ui.form input[type=file]:focus,.ui.form input[type=url]:focus,.ui.form input[type=number]:focus{color:rgba(0,0,0,.95);border-color:#85B7D9;border-radius:.28571429rem;background:#FFF;box-shadow:0 0 0 0 rgba(34,36,38,.35) inset}.ui.form textarea:focus{color:rgba(0,0,0,.95);border-color:#85B7D9;border-radius:.28571429rem;background:#FFF;box-shadow:0 0 0 0 rgba(34,36,38,.35) inset;-webkit-appearance:none}.ui.form.success .success.message:not(:empty){display:block}.ui.form.success .compact.success.message:not(:empty){display:inline-block}.ui.form.success .icon.success.message:not(:empty){display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.ui.form.warning .warning.message:not(:empty){display:block}.ui.form.warning .compact.warning.message:not(:empty){display:inline-block}.ui.form.warning .icon.warning.message:not(:empty){display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.ui.form.error .error.message:not(:empty){display:block}.ui.form.error .compact.error.message:not(:empty){display:inline-block}.ui.form.error .icon.error.message:not(:empty){display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.ui.form .field.error .input,.ui.form .field.error label,.ui.form .fields.error .field .input,.ui.form .fields.error .field label{color:#9F3A38}.ui.form .field.error .corner.label,.ui.form .fields.error .field .corner.label{border-color:#9F3A38;color:#FFF}.ui.form .field.error input:not([type]),.ui.form .field.error input[type=text],.ui.form .field.error input[type=email],.ui.form .field.error input[type=search],.ui.form .field.error input[type=password],.ui.form .field.error input[type=date],.ui.form .field.error input[type=datetime-local],.ui.form .field.error input[type=tel],.ui.form .field.error input[type=time],.ui.form .field.error input[type=file],.ui.form .field.error input[type=url],.ui.form .field.error input[type=number],.ui.form .field.error select,.ui.form .field.error textarea,.ui.form .fields.error .field input:not([type]),.ui.form .fields.error .field input[type=text],.ui.form .fields.error .field input[type=email],.ui.form .fields.error .field input[type=search],.ui.form .fields.error .field input[type=password],.ui.form .fields.error .field input[type=date],.ui.form .fields.error .field input[type=datetime-local],.ui.form .fields.error .field input[type=tel],.ui.form .fields.error .field input[type=time],.ui.form .fields.error .field input[type=file],.ui.form .fields.error .field input[type=url],.ui.form .fields.error .field input[type=number],.ui.form .fields.error .field select,.ui.form .fields.error .field textarea{background:#FFF6F6;border-color:#E0B4B4;color:#9F3A38;border-radius:'';box-shadow:none}.ui.form .field.error input:not([type]):focus,.ui.form .field.error input[type=text]:focus,.ui.form .field.error input[type=email]:focus,.ui.form .field.error input[type=search]:focus,.ui.form .field.error input[type=password]:focus,.ui.form .field.error input[type=date]:focus,.ui.form .field.error input[type=datetime-local]:focus,.ui.form .field.error input[type=tel]:focus,.ui.form .field.error input[type=time]:focus,.ui.form .field.error input[type=file]:focus,.ui.form .field.error input[type=url]:focus,.ui.form .field.error input[type=number]:focus,.ui.form .field.error select:focus,.ui.form .field.error textarea:focus{background:#FFF6F6;border-color:#E0B4B4;color:#9F3A38;-webkit-appearance:none;box-shadow:none}.ui.form .field.error select{-webkit-appearance:menulist-button}.ui.form .field.error .ui.dropdown,.ui.form .field.error .ui.dropdown .item,.ui.form .field.error .ui.dropdown .text,.ui.form .fields.error .field .ui.dropdown,.ui.form .fields.error .field .ui.dropdown .item{background:#FFF6F6;color:#9F3A38}.ui.form .field.error .ui.dropdown,.ui.form .field.error .ui.dropdown:hover,.ui.form .fields.error .field .ui.dropdown,.ui.form .fields.error .field .ui.dropdown:hover{border-color:#E0B4B4!important}.ui.form .field.error .ui.dropdown:hover .menu,.ui.form .fields.error .field .ui.dropdown:hover .menu{border-color:#E0B4B4}.ui.form .field.error .ui.multiple.selection.dropdown>.label,.ui.form .fields.error .field .ui.multiple.selection.dropdown>.label{background-color:#EACBCB;color:#9F3A38}.ui.form .field.error .ui.dropdown .menu .item:hover,.ui.form .field.error .ui.dropdown .menu .selected.item,.ui.form .fields.error .field .ui.dropdown .menu .item:hover,.ui.form .fields.error .field .ui.dropdown .menu .selected.item{background-color:#FBE7E7}.ui.form .field.error .ui.dropdown .menu .active.item,.ui.form .fields.error .field .ui.dropdown .menu .active.item{background-color:#FDCFCF!important}.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box,.ui.form .field.error .checkbox:not(.toggle):not(.slider) label,.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box,.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label{color:#9F3A38}.ui.form .field.error .checkbox:not(.toggle):not(.slider) .box:before,.ui.form .field.error .checkbox:not(.toggle):not(.slider) label:before,.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) .box:before,.ui.form .fields.error .field .checkbox:not(.toggle):not(.slider) label:before{background:#FFF6F6;border-color:#E0B4B4}.ui.form .field.error .checkbox .box:after,.ui.form .field.error .checkbox label:after,.ui.form .fields.error .field .checkbox .box:after,.ui.form .fields.error .field .checkbox label:after{color:#9F3A38}.ui.form .disabled.field,.ui.form .disabled.fields .field,.ui.form .field :disabled{pointer-events:none;opacity:.45}.ui.form .field.disabled>label,.ui.form .fields.disabled>label{opacity:.45}.ui.form .field.disabled :disabled{opacity:1}.ui.loading.form{position:relative;cursor:default;pointer-events:none}.ui.loading.form:before{position:absolute;content:'';top:0;left:0;background:rgba(255,255,255,.8);width:100%;height:100%;z-index:100}.ui.loading.form:after{position:absolute;content:'';top:50%;left:50%;margin:-1.5em 0 0 -1.5em;width:3em;height:3em;-webkit-animation:form-spin .6s linear;animation:form-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 rgba(0,0,0,.1) rgba(0,0,0,.1);border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent;visibility:visible;z-index:101}@-webkit-keyframes form-spin{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes form-spin{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ui.form .required.field>.checkbox:after,.ui.form .required.field>label:after,.ui.form .required.fields.grouped>label:after,.ui.form .required.fields:not(.grouped)>.field>.checkbox:after,.ui.form .required.fields:not(.grouped)>.field>label:after{margin:-.2em 0 0 .2em;content:'*';color:#DB2828}.ui.form .required.field>label:after,.ui.form .required.fields.grouped>label:after,.ui.form .required.fields:not(.grouped)>.field>label:after{display:inline-block;vertical-align:top}.ui.form .required.field>.checkbox:after,.ui.form .required.fields:not(.grouped)>.field>.checkbox:after{position:absolute;top:0;left:100%}.ui.form .inverted.segment .ui.checkbox .box,.ui.form .inverted.segment .ui.checkbox label,.ui.form .inverted.segment label,.ui.inverted.form .inline.field>label,.ui.inverted.form .inline.field>p,.ui.inverted.form .inline.fields .field>label,.ui.inverted.form .inline.fields .field>p,.ui.inverted.form .inline.fields>label,.ui.inverted.form .ui.checkbox .box,.ui.inverted.form .ui.checkbox label,.ui.inverted.form label{color:rgba(255,255,255,.9)}.ui.inverted.form input:not([type]),.ui.inverted.form input[type=text],.ui.inverted.form input[type=email],.ui.inverted.form input[type=search],.ui.inverted.form input[type=password],.ui.inverted.form input[type=date],.ui.inverted.form input[type=datetime-local],.ui.inverted.form input[type=tel],.ui.inverted.form input[type=time],.ui.inverted.form input[type=file],.ui.inverted.form input[type=url],.ui.inverted.form input[type=number]{background:#FFF;border-color:rgba(255,255,255,.1);color:rgba(0,0,0,.87);box-shadow:none}.ui.form .grouped.fields{display:block;margin:0 0 1em}.ui.form .grouped.fields:last-child{margin-bottom:0}.ui.form .grouped.fields>label{margin:0 0 .28571429rem;color:rgba(0,0,0,.87);font-size:.92857143em;font-weight:700;text-transform:none}.ui.form .grouped.fields .field,.ui.form .grouped.inline.fields .field{display:block;margin:.5em 0;padding:0}.ui.form .fields{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;margin:0 -.5em 1em}.ui.form .fields>.field{-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;padding-left:.5em;padding-right:.5em}.ui.form .fields>.field:first-child{border-left:none;box-shadow:none}.ui.form .two.fields>.field,.ui.form .two.fields>.fields{width:50%}.ui.form .three.fields>.field,.ui.form .three.fields>.fields{width:33.33333333%}.ui.form .four.fields>.field,.ui.form .four.fields>.fields{width:25%}.ui.form .five.fields>.field,.ui.form .five.fields>.fields{width:20%}.ui.form .six.fields>.field,.ui.form .six.fields>.fields{width:16.66666667%}.ui.form .seven.fields>.field,.ui.form .seven.fields>.fields{width:14.28571429%}.ui.form .eight.fields>.field,.ui.form .eight.fields>.fields{width:12.5%}.ui.form .nine.fields>.field,.ui.form .nine.fields>.fields{width:11.11111111%}.ui.form .ten.fields>.field,.ui.form .ten.fields>.fields{width:10%}@media only screen and (max-width:767px){.ui.form .fields{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.ui.form .eight.fields>.field,.ui.form .eight.fields>.fields,.ui.form .five.fields>.field,.ui.form .five.fields>.fields,.ui.form .four.fields>.field,.ui.form .four.fields>.fields,.ui.form .nine.fields>.field,.ui.form .nine.fields>.fields,.ui.form .seven.fields>.field,.ui.form .seven.fields>.fields,.ui.form .six.fields>.field,.ui.form .six.fields>.fields,.ui.form .ten.fields>.field,.ui.form .ten.fields>.fields,.ui.form .three.fields>.field,.ui.form .three.fields>.fields,.ui.form .two.fields>.field,.ui.form .two.fields>.fields,.ui.form [class*="equal width"].fields>.field,.ui[class*="equal width"].form .fields>.field{width:100%!important;margin:0 0 1em}}.ui.form .fields .wide.field{width:6.25%;padding-left:.5em;padding-right:.5em}.ui.form .one.wide.field{width:6.25%!important}.ui.form .two.wide.field{width:12.5%!important}.ui.form .three.wide.field{width:18.75%!important}.ui.form .four.wide.field{width:25%!important}.ui.form .five.wide.field{width:31.25%!important}.ui.form .six.wide.field{width:37.5%!important}.ui.form .seven.wide.field{width:43.75%!important}.ui.form .eight.wide.field{width:50%!important}.ui.form .nine.wide.field{width:56.25%!important}.ui.form .ten.wide.field{width:62.5%!important}.ui.form .eleven.wide.field{width:68.75%!important}.ui.form .twelve.wide.field{width:75%!important}.ui.form .thirteen.wide.field{width:81.25%!important}.ui.form .fourteen.wide.field{width:87.5%!important}.ui.form .fifteen.wide.field{width:93.75%!important}.ui.form .sixteen.wide.field{width:100%!important}@media only screen and (max-width:767px){.ui.form .fields>.eight.wide.field,.ui.form .fields>.eleven.wide.field,.ui.form .fields>.fifteen.wide.field,.ui.form .fields>.five.wide.field,.ui.form .fields>.four.wide.field,.ui.form .fields>.fourteen.wide.field,.ui.form .fields>.nine.wide.field,.ui.form .fields>.seven.wide.field,.ui.form .fields>.six.wide.field,.ui.form .fields>.sixteen.wide.field,.ui.form .fields>.ten.wide.field,.ui.form .fields>.thirteen.wide.field,.ui.form .fields>.three.wide.field,.ui.form .fields>.twelve.wide.field,.ui.form .fields>.two.wide.field,.ui.form .five.fields>.field,.ui.form .five.fields>.fields,.ui.form .four.fields>.field,.ui.form .four.fields>.fields,.ui.form .three.fields>.field,.ui.form .three.fields>.fields,.ui.form .two.fields>.field,.ui.form .two.fields>.fields{width:100%!important}.ui.form .fields{margin-bottom:0}}.ui.form [class*="equal width"].fields>.field,.ui[class*="equal width"].form .fields>.field{width:100%;-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}.ui.form .inline.fields{margin:0 0 1em;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}.ui.form .inline.fields .field{margin:0;padding:0 1em 0 0}.ui.form .inline.field>label,.ui.form .inline.field>p,.ui.form .inline.fields .field>label,.ui.form .inline.fields .field>p,.ui.form .inline.fields>label{display:inline-block;width:auto;margin-top:0;margin-bottom:0;vertical-align:baseline;font-size:.92857143em;font-weight:700;color:rgba(0,0,0,.87);text-transform:none}.ui.form .inline.fields>label{margin:.035714em 1em 0 0}.ui.form .inline.field>input,.ui.form .inline.field>select,.ui.form .inline.fields .field>input,.ui.form .inline.fields .field>select{display:inline-block;width:auto;margin-top:0;margin-bottom:0;vertical-align:middle;font-size:1em}.ui.form .inline.field>:first-child,.ui.form .inline.fields .field>:first-child{margin:0 .85714286em 0 0}.ui.form .inline.field>:only-child,.ui.form .inline.fields .field>:only-child{margin:0}.ui.form .inline.fields .wide.field{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.ui.form .inline.fields .wide.field>input,.ui.form .inline.fields .wide.field>select{width:100%}.ui.mini.form{font-size:.78571429rem}.ui.tiny.form{font-size:.85714286rem}.ui.small.form{font-size:.92857143rem}.ui.form{font-size:1rem}.ui.large.form{font-size:1.14285714rem}.ui.big.form{font-size:1.28571429rem}.ui.huge.form{font-size:1.42857143rem}.ui.massive.form{font-size:1.71428571rem}.ui.grid{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;padding:0;margin:-1rem}.ui.relaxed.grid{margin-left:-1.5rem;margin-right:-1.5rem}.ui[class*="very relaxed"].grid{margin-left:-2.5rem;margin-right:-2.5rem}.ui.grid+.grid{margin-top:1rem}.ui.grid>.column:not(.row),.ui.grid>.row>.column{position:relative;display:inline-block;width:6.25%;padding-left:1rem;padding-right:1rem;vertical-align:top}.ui.grid>*{padding-left:1rem;padding-right:1rem}.ui.grid>.row{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:inherit;-webkit-justify-content:inherit;-ms-flex-pack:inherit;justify-content:inherit;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%!important;padding:1rem 0}.ui.grid>.column:not(.row){padding-top:1rem;padding-bottom:1rem}.ui.grid>.row>.column{margin-top:0;margin-bottom:0}.ui.grid>.row>.column>img,.ui.grid>.row>img{max-width:100%}.ui.grid>.ui.grid:first-child{margin-top:0}.ui.grid>.ui.grid:last-child{margin-bottom:0}.ui.aligned.grid .column>.segment:not(.compact):not(.attached),.ui.grid .aligned.row>.column>.segment:not(.compact):not(.attached){width:100%}.ui.grid .row+.ui.divider{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:1rem}.ui.grid .column+.ui.vertical.divider{height:calc(50% - 1rem)}.ui.grid>.column:last-child>.horizontal.segment,.ui.grid>.row>.column:last-child>.horizontal.segment{box-shadow:none}@media only screen and (max-width:767px){.ui.page.grid{width:auto;padding-left:0;padding-right:0;margin-left:0;margin-right:0}}@media only screen and (min-width:768px) and (max-width:991px){.ui.page.grid{width:auto;margin-left:0;margin-right:0;padding-left:2em;padding-right:2em}}@media only screen and (min-width:992px) and (max-width:1199px){.ui.page.grid{width:auto;margin-left:0;margin-right:0;padding-left:3%;padding-right:3%}}@media only screen and (min-width:1200px) and (max-width:1919px){.ui.page.grid{width:auto;margin-left:0;margin-right:0;padding-left:15%;padding-right:15%}}@media only screen and (min-width:1920px){.ui.page.grid{width:auto;margin-left:0;margin-right:0;padding-left:23%;padding-right:23%}}.ui.grid>.column:only-child,.ui.grid>.row>.column:only-child,.ui[class*="one column"].grid>.column:not(.row),.ui[class*="one column"].grid>.row>.column{width:100%}.ui[class*="two column"].grid>.column:not(.row),.ui[class*="two column"].grid>.row>.column{width:50%}.ui[class*="three column"].grid>.column:not(.row),.ui[class*="three column"].grid>.row>.column{width:33.33333333%}.ui[class*="four column"].grid>.column:not(.row),.ui[class*="four column"].grid>.row>.column{width:25%}.ui[class*="five column"].grid>.column:not(.row),.ui[class*="five column"].grid>.row>.column{width:20%}.ui[class*="six column"].grid>.column:not(.row),.ui[class*="six column"].grid>.row>.column{width:16.66666667%}.ui[class*="seven column"].grid>.column:not(.row),.ui[class*="seven column"].grid>.row>.column{width:14.28571429%}.ui[class*="eight column"].grid>.column:not(.row),.ui[class*="eight column"].grid>.row>.column{width:12.5%}.ui[class*="nine column"].grid>.column:not(.row),.ui[class*="nine column"].grid>.row>.column{width:11.11111111%}.ui[class*="ten column"].grid>.column:not(.row),.ui[class*="ten column"].grid>.row>.column{width:10%}.ui[class*="eleven column"].grid>.column:not(.row),.ui[class*="eleven column"].grid>.row>.column{width:9.09090909%}.ui[class*="twelve column"].grid>.column:not(.row),.ui[class*="twelve column"].grid>.row>.column{width:8.33333333%}.ui[class*="thirteen column"].grid>.column:not(.row),.ui[class*="thirteen column"].grid>.row>.column{width:7.69230769%}.ui[class*="fourteen column"].grid>.column:not(.row),.ui[class*="fourteen column"].grid>.row>.column{width:7.14285714%}.ui[class*="fifteen column"].grid>.column:not(.row),.ui[class*="fifteen column"].grid>.row>.column{width:6.66666667%}.ui[class*="sixteen column"].grid>.column:not(.row),.ui[class*="sixteen column"].grid>.row>.column{width:6.25%}.ui.grid>[class*="one column"].row>.column{width:100%!important}.ui.grid>[class*="two column"].row>.column{width:50%!important}.ui.grid>[class*="three column"].row>.column{width:33.33333333%!important}.ui.grid>[class*="four column"].row>.column{width:25%!important}.ui.grid>[class*="five column"].row>.column{width:20%!important}.ui.grid>[class*="six column"].row>.column{width:16.66666667%!important}.ui.grid>[class*="seven column"].row>.column{width:14.28571429%!important}.ui.grid>[class*="eight column"].row>.column{width:12.5%!important}.ui.grid>[class*="nine column"].row>.column{width:11.11111111%!important}.ui.grid>[class*="ten column"].row>.column{width:10%!important}.ui.grid>[class*="eleven column"].row>.column{width:9.09090909%!important}.ui.grid>[class*="twelve column"].row>.column{width:8.33333333%!important}.ui.grid>[class*="thirteen column"].row>.column{width:7.69230769%!important}.ui.grid>[class*="fourteen column"].row>.column{width:7.14285714%!important}.ui.grid>[class*="fifteen column"].row>.column{width:6.66666667%!important}.ui.grid>[class*="sixteen column"].row>.column{width:6.25%!important}.ui.celled.page.grid{box-shadow:none}.ui.column.grid>[class*="one wide"].column,.ui.grid>.column.row>[class*="one wide"].column,.ui.grid>.row>[class*="one wide"].column,.ui.grid>[class*="one wide"].column{width:6.25%!important}.ui.column.grid>[class*="two wide"].column,.ui.grid>.column.row>[class*="two wide"].column,.ui.grid>.row>[class*="two wide"].column,.ui.grid>[class*="two wide"].column{width:12.5%!important}.ui.column.grid>[class*="three wide"].column,.ui.grid>.column.row>[class*="three wide"].column,.ui.grid>.row>[class*="three wide"].column,.ui.grid>[class*="three wide"].column{width:18.75%!important}.ui.column.grid>[class*="four wide"].column,.ui.grid>.column.row>[class*="four wide"].column,.ui.grid>.row>[class*="four wide"].column,.ui.grid>[class*="four wide"].column{width:25%!important}.ui.column.grid>[class*="five wide"].column,.ui.grid>.column.row>[class*="five wide"].column,.ui.grid>.row>[class*="five wide"].column,.ui.grid>[class*="five wide"].column{width:31.25%!important}.ui.column.grid>[class*="six wide"].column,.ui.grid>.column.row>[class*="six wide"].column,.ui.grid>.row>[class*="six wide"].column,.ui.grid>[class*="six wide"].column{width:37.5%!important}.ui.column.grid>[class*="seven wide"].column,.ui.grid>.column.row>[class*="seven wide"].column,.ui.grid>.row>[class*="seven wide"].column,.ui.grid>[class*="seven wide"].column{width:43.75%!important}.ui.column.grid>[class*="eight wide"].column,.ui.grid>.column.row>[class*="eight wide"].column,.ui.grid>.row>[class*="eight wide"].column,.ui.grid>[class*="eight wide"].column{width:50%!important}.ui.column.grid>[class*="nine wide"].column,.ui.grid>.column.row>[class*="nine wide"].column,.ui.grid>.row>[class*="nine wide"].column,.ui.grid>[class*="nine wide"].column{width:56.25%!important}.ui.column.grid>[class*="ten wide"].column,.ui.grid>.column.row>[class*="ten wide"].column,.ui.grid>.row>[class*="ten wide"].column,.ui.grid>[class*="ten wide"].column{width:62.5%!important}.ui.column.grid>[class*="eleven wide"].column,.ui.grid>.column.row>[class*="eleven wide"].column,.ui.grid>.row>[class*="eleven wide"].column,.ui.grid>[class*="eleven wide"].column{width:68.75%!important}.ui.column.grid>[class*="twelve wide"].column,.ui.grid>.column.row>[class*="twelve wide"].column,.ui.grid>.row>[class*="twelve wide"].column,.ui.grid>[class*="twelve wide"].column{width:75%!important}.ui.column.grid>[class*="thirteen wide"].column,.ui.grid>.column.row>[class*="thirteen wide"].column,.ui.grid>.row>[class*="thirteen wide"].column,.ui.grid>[class*="thirteen wide"].column{width:81.25%!important}.ui.column.grid>[class*="fourteen wide"].column,.ui.grid>.column.row>[class*="fourteen wide"].column,.ui.grid>.row>[class*="fourteen wide"].column,.ui.grid>[class*="fourteen wide"].column{width:87.5%!important}.ui.column.grid>[class*="fifteen wide"].column,.ui.grid>.column.row>[class*="fifteen wide"].column,.ui.grid>.row>[class*="fifteen wide"].column,.ui.grid>[class*="fifteen wide"].column{width:93.75%!important}.ui.column.grid>[class*="sixteen wide"].column,.ui.grid>.column.row>[class*="sixteen wide"].column,.ui.grid>.row>[class*="sixteen wide"].column,.ui.grid>[class*="sixteen wide"].column{width:100%!important}@media only screen and (min-width:320px) and (max-width:767px){.ui.column.grid>[class*="one wide mobile"].column,.ui.grid>.column.row>[class*="one wide mobile"].column,.ui.grid>.row>[class*="one wide mobile"].column,.ui.grid>[class*="one wide mobile"].column{width:6.25%!important}.ui.column.grid>[class*="two wide mobile"].column,.ui.grid>.column.row>[class*="two wide mobile"].column,.ui.grid>.row>[class*="two wide mobile"].column,.ui.grid>[class*="two wide mobile"].column{width:12.5%!important}.ui.column.grid>[class*="three wide mobile"].column,.ui.grid>.column.row>[class*="three wide mobile"].column,.ui.grid>.row>[class*="three wide mobile"].column,.ui.grid>[class*="three wide mobile"].column{width:18.75%!important}.ui.column.grid>[class*="four wide mobile"].column,.ui.grid>.column.row>[class*="four wide mobile"].column,.ui.grid>.row>[class*="four wide mobile"].column,.ui.grid>[class*="four wide mobile"].column{width:25%!important}.ui.column.grid>[class*="five wide mobile"].column,.ui.grid>.column.row>[class*="five wide mobile"].column,.ui.grid>.row>[class*="five wide mobile"].column,.ui.grid>[class*="five wide mobile"].column{width:31.25%!important}.ui.column.grid>[class*="six wide mobile"].column,.ui.grid>.column.row>[class*="six wide mobile"].column,.ui.grid>.row>[class*="six wide mobile"].column,.ui.grid>[class*="six wide mobile"].column{width:37.5%!important}.ui.column.grid>[class*="seven wide mobile"].column,.ui.grid>.column.row>[class*="seven wide mobile"].column,.ui.grid>.row>[class*="seven wide mobile"].column,.ui.grid>[class*="seven wide mobile"].column{width:43.75%!important}.ui.column.grid>[class*="eight wide mobile"].column,.ui.grid>.column.row>[class*="eight wide mobile"].column,.ui.grid>.row>[class*="eight wide mobile"].column,.ui.grid>[class*="eight wide mobile"].column{width:50%!important}.ui.column.grid>[class*="nine wide mobile"].column,.ui.grid>.column.row>[class*="nine wide mobile"].column,.ui.grid>.row>[class*="nine wide mobile"].column,.ui.grid>[class*="nine wide mobile"].column{width:56.25%!important}.ui.column.grid>[class*="ten wide mobile"].column,.ui.grid>.column.row>[class*="ten wide mobile"].column,.ui.grid>.row>[class*="ten wide mobile"].column,.ui.grid>[class*="ten wide mobile"].column{width:62.5%!important}.ui.column.grid>[class*="eleven wide mobile"].column,.ui.grid>.column.row>[class*="eleven wide mobile"].column,.ui.grid>.row>[class*="eleven wide mobile"].column,.ui.grid>[class*="eleven wide mobile"].column{width:68.75%!important}.ui.column.grid>[class*="twelve wide mobile"].column,.ui.grid>.column.row>[class*="twelve wide mobile"].column,.ui.grid>.row>[class*="twelve wide mobile"].column,.ui.grid>[class*="twelve wide mobile"].column{width:75%!important}.ui.column.grid>[class*="thirteen wide mobile"].column,.ui.grid>.column.row>[class*="thirteen wide mobile"].column,.ui.grid>.row>[class*="thirteen wide mobile"].column,.ui.grid>[class*="thirteen wide mobile"].column{width:81.25%!important}.ui.column.grid>[class*="fourteen wide mobile"].column,.ui.grid>.column.row>[class*="fourteen wide mobile"].column,.ui.grid>.row>[class*="fourteen wide mobile"].column,.ui.grid>[class*="fourteen wide mobile"].column{width:87.5%!important}.ui.column.grid>[class*="fifteen wide mobile"].column,.ui.grid>.column.row>[class*="fifteen wide mobile"].column,.ui.grid>.row>[class*="fifteen wide mobile"].column,.ui.grid>[class*="fifteen wide mobile"].column{width:93.75%!important}.ui.column.grid>[class*="sixteen wide mobile"].column,.ui.grid>.column.row>[class*="sixteen wide mobile"].column,.ui.grid>.row>[class*="sixteen wide mobile"].column,.ui.grid>[class*="sixteen wide mobile"].column{width:100%!important}}@media only screen and (min-width:768px) and (max-width:991px){.ui.column.grid>[class*="one wide tablet"].column,.ui.grid>.column.row>[class*="one wide tablet"].column,.ui.grid>.row>[class*="one wide tablet"].column,.ui.grid>[class*="one wide tablet"].column{width:6.25%!important}.ui.column.grid>[class*="two wide tablet"].column,.ui.grid>.column.row>[class*="two wide tablet"].column,.ui.grid>.row>[class*="two wide tablet"].column,.ui.grid>[class*="two wide tablet"].column{width:12.5%!important}.ui.column.grid>[class*="three wide tablet"].column,.ui.grid>.column.row>[class*="three wide tablet"].column,.ui.grid>.row>[class*="three wide tablet"].column,.ui.grid>[class*="three wide tablet"].column{width:18.75%!important}.ui.column.grid>[class*="four wide tablet"].column,.ui.grid>.column.row>[class*="four wide tablet"].column,.ui.grid>.row>[class*="four wide tablet"].column,.ui.grid>[class*="four wide tablet"].column{width:25%!important}.ui.column.grid>[class*="five wide tablet"].column,.ui.grid>.column.row>[class*="five wide tablet"].column,.ui.grid>.row>[class*="five wide tablet"].column,.ui.grid>[class*="five wide tablet"].column{width:31.25%!important}.ui.column.grid>[class*="six wide tablet"].column,.ui.grid>.column.row>[class*="six wide tablet"].column,.ui.grid>.row>[class*="six wide tablet"].column,.ui.grid>[class*="six wide tablet"].column{width:37.5%!important}.ui.column.grid>[class*="seven wide tablet"].column,.ui.grid>.column.row>[class*="seven wide tablet"].column,.ui.grid>.row>[class*="seven wide tablet"].column,.ui.grid>[class*="seven wide tablet"].column{width:43.75%!important}.ui.column.grid>[class*="eight wide tablet"].column,.ui.grid>.column.row>[class*="eight wide tablet"].column,.ui.grid>.row>[class*="eight wide tablet"].column,.ui.grid>[class*="eight wide tablet"].column{width:50%!important}.ui.column.grid>[class*="nine wide tablet"].column,.ui.grid>.column.row>[class*="nine wide tablet"].column,.ui.grid>.row>[class*="nine wide tablet"].column,.ui.grid>[class*="nine wide tablet"].column{width:56.25%!important}.ui.column.grid>[class*="ten wide tablet"].column,.ui.grid>.column.row>[class*="ten wide tablet"].column,.ui.grid>.row>[class*="ten wide tablet"].column,.ui.grid>[class*="ten wide tablet"].column{width:62.5%!important}.ui.column.grid>[class*="eleven wide tablet"].column,.ui.grid>.column.row>[class*="eleven wide tablet"].column,.ui.grid>.row>[class*="eleven wide tablet"].column,.ui.grid>[class*="eleven wide tablet"].column{width:68.75%!important}.ui.column.grid>[class*="twelve wide tablet"].column,.ui.grid>.column.row>[class*="twelve wide tablet"].column,.ui.grid>.row>[class*="twelve wide tablet"].column,.ui.grid>[class*="twelve wide tablet"].column{width:75%!important}.ui.column.grid>[class*="thirteen wide tablet"].column,.ui.grid>.column.row>[class*="thirteen wide tablet"].column,.ui.grid>.row>[class*="thirteen wide tablet"].column,.ui.grid>[class*="thirteen wide tablet"].column{width:81.25%!important}.ui.column.grid>[class*="fourteen wide tablet"].column,.ui.grid>.column.row>[class*="fourteen wide tablet"].column,.ui.grid>.row>[class*="fourteen wide tablet"].column,.ui.grid>[class*="fourteen wide tablet"].column{width:87.5%!important}.ui.column.grid>[class*="fifteen wide tablet"].column,.ui.grid>.column.row>[class*="fifteen wide tablet"].column,.ui.grid>.row>[class*="fifteen wide tablet"].column,.ui.grid>[class*="fifteen wide tablet"].column{width:93.75%!important}.ui.column.grid>[class*="sixteen wide tablet"].column,.ui.grid>.column.row>[class*="sixteen wide tablet"].column,.ui.grid>.row>[class*="sixteen wide tablet"].column,.ui.grid>[class*="sixteen wide tablet"].column{width:100%!important}}@media only screen and (min-width:992px){.ui.column.grid>[class*="one wide computer"].column,.ui.grid>.column.row>[class*="one wide computer"].column,.ui.grid>.row>[class*="one wide computer"].column,.ui.grid>[class*="one wide computer"].column{width:6.25%!important}.ui.column.grid>[class*="two wide computer"].column,.ui.grid>.column.row>[class*="two wide computer"].column,.ui.grid>.row>[class*="two wide computer"].column,.ui.grid>[class*="two wide computer"].column{width:12.5%!important}.ui.column.grid>[class*="three wide computer"].column,.ui.grid>.column.row>[class*="three wide computer"].column,.ui.grid>.row>[class*="three wide computer"].column,.ui.grid>[class*="three wide computer"].column{width:18.75%!important}.ui.column.grid>[class*="four wide computer"].column,.ui.grid>.column.row>[class*="four wide computer"].column,.ui.grid>.row>[class*="four wide computer"].column,.ui.grid>[class*="four wide computer"].column{width:25%!important}.ui.column.grid>[class*="five wide computer"].column,.ui.grid>.column.row>[class*="five wide computer"].column,.ui.grid>.row>[class*="five wide computer"].column,.ui.grid>[class*="five wide computer"].column{width:31.25%!important}.ui.column.grid>[class*="six wide computer"].column,.ui.grid>.column.row>[class*="six wide computer"].column,.ui.grid>.row>[class*="six wide computer"].column,.ui.grid>[class*="six wide computer"].column{width:37.5%!important}.ui.column.grid>[class*="seven wide computer"].column,.ui.grid>.column.row>[class*="seven wide computer"].column,.ui.grid>.row>[class*="seven wide computer"].column,.ui.grid>[class*="seven wide computer"].column{width:43.75%!important}.ui.column.grid>[class*="eight wide computer"].column,.ui.grid>.column.row>[class*="eight wide computer"].column,.ui.grid>.row>[class*="eight wide computer"].column,.ui.grid>[class*="eight wide computer"].column{width:50%!important}.ui.column.grid>[class*="nine wide computer"].column,.ui.grid>.column.row>[class*="nine wide computer"].column,.ui.grid>.row>[class*="nine wide computer"].column,.ui.grid>[class*="nine wide computer"].column{width:56.25%!important}.ui.column.grid>[class*="ten wide computer"].column,.ui.grid>.column.row>[class*="ten wide computer"].column,.ui.grid>.row>[class*="ten wide computer"].column,.ui.grid>[class*="ten wide computer"].column{width:62.5%!important}.ui.column.grid>[class*="eleven wide computer"].column,.ui.grid>.column.row>[class*="eleven wide computer"].column,.ui.grid>.row>[class*="eleven wide computer"].column,.ui.grid>[class*="eleven wide computer"].column{width:68.75%!important}.ui.column.grid>[class*="twelve wide computer"].column,.ui.grid>.column.row>[class*="twelve wide computer"].column,.ui.grid>.row>[class*="twelve wide computer"].column,.ui.grid>[class*="twelve wide computer"].column{width:75%!important}.ui.column.grid>[class*="thirteen wide computer"].column,.ui.grid>.column.row>[class*="thirteen wide computer"].column,.ui.grid>.row>[class*="thirteen wide computer"].column,.ui.grid>[class*="thirteen wide computer"].column{width:81.25%!important}.ui.column.grid>[class*="fourteen wide computer"].column,.ui.grid>.column.row>[class*="fourteen wide computer"].column,.ui.grid>.row>[class*="fourteen wide computer"].column,.ui.grid>[class*="fourteen wide computer"].column{width:87.5%!important}.ui.column.grid>[class*="fifteen wide computer"].column,.ui.grid>.column.row>[class*="fifteen wide computer"].column,.ui.grid>.row>[class*="fifteen wide computer"].column,.ui.grid>[class*="fifteen wide computer"].column{width:93.75%!important}.ui.column.grid>[class*="sixteen wide computer"].column,.ui.grid>.column.row>[class*="sixteen wide computer"].column,.ui.grid>.row>[class*="sixteen wide computer"].column,.ui.grid>[class*="sixteen wide computer"].column{width:100%!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.ui.column.grid>[class*="one wide large screen"].column,.ui.grid>.column.row>[class*="one wide large screen"].column,.ui.grid>.row>[class*="one wide large screen"].column,.ui.grid>[class*="one wide large screen"].column{width:6.25%!important}.ui.column.grid>[class*="two wide large screen"].column,.ui.grid>.column.row>[class*="two wide large screen"].column,.ui.grid>.row>[class*="two wide large screen"].column,.ui.grid>[class*="two wide large screen"].column{width:12.5%!important}.ui.column.grid>[class*="three wide large screen"].column,.ui.grid>.column.row>[class*="three wide large screen"].column,.ui.grid>.row>[class*="three wide large screen"].column,.ui.grid>[class*="three wide large screen"].column{width:18.75%!important}.ui.column.grid>[class*="four wide large screen"].column,.ui.grid>.column.row>[class*="four wide large screen"].column,.ui.grid>.row>[class*="four wide large screen"].column,.ui.grid>[class*="four wide large screen"].column{width:25%!important}.ui.column.grid>[class*="five wide large screen"].column,.ui.grid>.column.row>[class*="five wide large screen"].column,.ui.grid>.row>[class*="five wide large screen"].column,.ui.grid>[class*="five wide large screen"].column{width:31.25%!important}.ui.column.grid>[class*="six wide large screen"].column,.ui.grid>.column.row>[class*="six wide large screen"].column,.ui.grid>.row>[class*="six wide large screen"].column,.ui.grid>[class*="six wide large screen"].column{width:37.5%!important}.ui.column.grid>[class*="seven wide large screen"].column,.ui.grid>.column.row>[class*="seven wide large screen"].column,.ui.grid>.row>[class*="seven wide large screen"].column,.ui.grid>[class*="seven wide large screen"].column{width:43.75%!important}.ui.column.grid>[class*="eight wide large screen"].column,.ui.grid>.column.row>[class*="eight wide large screen"].column,.ui.grid>.row>[class*="eight wide large screen"].column,.ui.grid>[class*="eight wide large screen"].column{width:50%!important}.ui.column.grid>[class*="nine wide large screen"].column,.ui.grid>.column.row>[class*="nine wide large screen"].column,.ui.grid>.row>[class*="nine wide large screen"].column,.ui.grid>[class*="nine wide large screen"].column{width:56.25%!important}.ui.column.grid>[class*="ten wide large screen"].column,.ui.grid>.column.row>[class*="ten wide large screen"].column,.ui.grid>.row>[class*="ten wide large screen"].column,.ui.grid>[class*="ten wide large screen"].column{width:62.5%!important}.ui.column.grid>[class*="eleven wide large screen"].column,.ui.grid>.column.row>[class*="eleven wide large screen"].column,.ui.grid>.row>[class*="eleven wide large screen"].column,.ui.grid>[class*="eleven wide large screen"].column{width:68.75%!important}.ui.column.grid>[class*="twelve wide large screen"].column,.ui.grid>.column.row>[class*="twelve wide large screen"].column,.ui.grid>.row>[class*="twelve wide large screen"].column,.ui.grid>[class*="twelve wide large screen"].column{width:75%!important}.ui.column.grid>[class*="thirteen wide large screen"].column,.ui.grid>.column.row>[class*="thirteen wide large screen"].column,.ui.grid>.row>[class*="thirteen wide large screen"].column,.ui.grid>[class*="thirteen wide large screen"].column{width:81.25%!important}.ui.column.grid>[class*="fourteen wide large screen"].column,.ui.grid>.column.row>[class*="fourteen wide large screen"].column,.ui.grid>.row>[class*="fourteen wide large screen"].column,.ui.grid>[class*="fourteen wide large screen"].column{width:87.5%!important}.ui.column.grid>[class*="fifteen wide large screen"].column,.ui.grid>.column.row>[class*="fifteen wide large screen"].column,.ui.grid>.row>[class*="fifteen wide large screen"].column,.ui.grid>[class*="fifteen wide large screen"].column{width:93.75%!important}.ui.column.grid>[class*="sixteen wide large screen"].column,.ui.grid>.column.row>[class*="sixteen wide large screen"].column,.ui.grid>.row>[class*="sixteen wide large screen"].column,.ui.grid>[class*="sixteen wide large screen"].column{width:100%!important}}@media only screen and (min-width:1920px){.ui.column.grid>[class*="one wide widescreen"].column,.ui.grid>.column.row>[class*="one wide widescreen"].column,.ui.grid>.row>[class*="one wide widescreen"].column,.ui.grid>[class*="one wide widescreen"].column{width:6.25%!important}.ui.column.grid>[class*="two wide widescreen"].column,.ui.grid>.column.row>[class*="two wide widescreen"].column,.ui.grid>.row>[class*="two wide widescreen"].column,.ui.grid>[class*="two wide widescreen"].column{width:12.5%!important}.ui.column.grid>[class*="three wide widescreen"].column,.ui.grid>.column.row>[class*="three wide widescreen"].column,.ui.grid>.row>[class*="three wide widescreen"].column,.ui.grid>[class*="three wide widescreen"].column{width:18.75%!important}.ui.column.grid>[class*="four wide widescreen"].column,.ui.grid>.column.row>[class*="four wide widescreen"].column,.ui.grid>.row>[class*="four wide widescreen"].column,.ui.grid>[class*="four wide widescreen"].column{width:25%!important}.ui.column.grid>[class*="five wide widescreen"].column,.ui.grid>.column.row>[class*="five wide widescreen"].column,.ui.grid>.row>[class*="five wide widescreen"].column,.ui.grid>[class*="five wide widescreen"].column{width:31.25%!important}.ui.column.grid>[class*="six wide widescreen"].column,.ui.grid>.column.row>[class*="six wide widescreen"].column,.ui.grid>.row>[class*="six wide widescreen"].column,.ui.grid>[class*="six wide widescreen"].column{width:37.5%!important}.ui.column.grid>[class*="seven wide widescreen"].column,.ui.grid>.column.row>[class*="seven wide widescreen"].column,.ui.grid>.row>[class*="seven wide widescreen"].column,.ui.grid>[class*="seven wide widescreen"].column{width:43.75%!important}.ui.column.grid>[class*="eight wide widescreen"].column,.ui.grid>.column.row>[class*="eight wide widescreen"].column,.ui.grid>.row>[class*="eight wide widescreen"].column,.ui.grid>[class*="eight wide widescreen"].column{width:50%!important}.ui.column.grid>[class*="nine wide widescreen"].column,.ui.grid>.column.row>[class*="nine wide widescreen"].column,.ui.grid>.row>[class*="nine wide widescreen"].column,.ui.grid>[class*="nine wide widescreen"].column{width:56.25%!important}.ui.column.grid>[class*="ten wide widescreen"].column,.ui.grid>.column.row>[class*="ten wide widescreen"].column,.ui.grid>.row>[class*="ten wide widescreen"].column,.ui.grid>[class*="ten wide widescreen"].column{width:62.5%!important}.ui.column.grid>[class*="eleven wide widescreen"].column,.ui.grid>.column.row>[class*="eleven wide widescreen"].column,.ui.grid>.row>[class*="eleven wide widescreen"].column,.ui.grid>[class*="eleven wide widescreen"].column{width:68.75%!important}.ui.column.grid>[class*="twelve wide widescreen"].column,.ui.grid>.column.row>[class*="twelve wide widescreen"].column,.ui.grid>.row>[class*="twelve wide widescreen"].column,.ui.grid>[class*="twelve wide widescreen"].column{width:75%!important}.ui.column.grid>[class*="thirteen wide widescreen"].column,.ui.grid>.column.row>[class*="thirteen wide widescreen"].column,.ui.grid>.row>[class*="thirteen wide widescreen"].column,.ui.grid>[class*="thirteen wide widescreen"].column{width:81.25%!important}.ui.column.grid>[class*="fourteen wide widescreen"].column,.ui.grid>.column.row>[class*="fourteen wide widescreen"].column,.ui.grid>.row>[class*="fourteen wide widescreen"].column,.ui.grid>[class*="fourteen wide widescreen"].column{width:87.5%!important}.ui.column.grid>[class*="fifteen wide widescreen"].column,.ui.grid>.column.row>[class*="fifteen wide widescreen"].column,.ui.grid>.row>[class*="fifteen wide widescreen"].column,.ui.grid>[class*="fifteen wide widescreen"].column{width:93.75%!important}.ui.column.grid>[class*="sixteen wide widescreen"].column,.ui.grid>.column.row>[class*="sixteen wide widescreen"].column,.ui.grid>.row>[class*="sixteen wide widescreen"].column,.ui.grid>[class*="sixteen wide widescreen"].column{width:100%!important}}.ui.centered.grid,.ui.centered.grid>.row,.ui.grid>.centered.row{text-align:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.ui.centered.grid>.column:not(.aligned):not(.justified):not(.row),.ui.centered.grid>.row>.column:not(.aligned):not(.justified),.ui.grid .centered.row>.column:not(.aligned):not(.justified){text-align:left}.ui.grid>.centered.column,.ui.grid>.row>.centered.column{display:block;margin-left:auto;margin-right:auto}.ui.grid>.relaxed.row>.column,.ui.relaxed.grid>.column:not(.row),.ui.relaxed.grid>.row>.column{padding-left:1.5rem;padding-right:1.5rem}.ui.grid>[class*="very relaxed"].row>.column,.ui[class*="very relaxed"].grid>.column:not(.row),.ui[class*="very relaxed"].grid>.row>.column{padding-left:2.5rem;padding-right:2.5rem}.ui.grid .relaxed.row+.ui.divider,.ui.relaxed.grid .row+.ui.divider{margin-left:1.5rem;margin-right:1.5rem}.ui.grid [class*="very relaxed"].row+.ui.divider,.ui[class*="very relaxed"].grid .row+.ui.divider{margin-left:2.5rem;margin-right:2.5rem}.ui.padded.grid:not(.vertically):not(.horizontally){margin:0!important}[class*="horizontally padded"].ui.grid{margin-left:0!important;margin-right:0!important}[class*="vertically padded"].ui.grid{margin-top:0!important;margin-bottom:0!important}.ui.grid [class*="left floated"].column{margin-right:auto}.ui.grid [class*="right floated"].column{margin-left:auto}.ui.divided.grid:not([class*="vertically divided"])>.column:not(.row),.ui.divided.grid:not([class*="vertically divided"])>.row>.column{box-shadow:-1px 0 0 0 rgba(34,36,38,.15)}.ui[class*="vertically divided"].grid>.column:not(.row),.ui[class*="vertically divided"].grid>.row>.column{margin-top:1rem;margin-bottom:1rem;padding-top:0;padding-bottom:0}.ui[class*="vertically divided"].grid>.row{margin-top:0;margin-bottom:0;position:relative}.ui.divided.grid:not([class*="vertically divided"])>.column:first-child,.ui.divided.grid:not([class*="vertically divided"])>.row>.column:first-child{box-shadow:none}.ui[class*="vertically divided"].grid>.row:first-child>.column{margin-top:0}.ui.grid>.divided.row>.column{box-shadow:-1px 0 0 0 rgba(34,36,38,.15)}.ui.grid>.divided.row>.column:first-child{box-shadow:none}.ui[class*="vertically divided"].grid>.row:before{position:absolute;content:"";top:0;left:0;width:calc(100% - 2rem);height:1px;margin:0 1rem;box-shadow:0 -1px 0 0 rgba(34,36,38,.15)}.ui.padded.divided.grid:not(.vertically):not(.horizontally),[class*="horizontally padded"].ui.divided.grid{width:100%}.ui[class*="vertically divided"].grid>.row:first-child:before{box-shadow:none}.ui.inverted.divided.grid:not([class*="vertically divided"])>.column:not(.row),.ui.inverted.divided.grid:not([class*="vertically divided"])>.row>.column{box-shadow:-1px 0 0 0 rgba(255,255,255,.1)}.ui.inverted.divided.grid:not([class*="vertically divided"])>.column:not(.row):first-child,.ui.inverted.divided.grid:not([class*="vertically divided"])>.row>.column:first-child{box-shadow:none}.ui.inverted[class*="vertically divided"].grid>.row:before{box-shadow:0 -1px 0 0 rgba(255,255,255,.1)}.ui.relaxed[class*="vertically divided"].grid>.row:before{margin-left:1.5rem;margin-right:1.5rem;width:calc(100% - 3rem)}.ui[class*="very relaxed"][class*="vertically divided"].grid>.row:before{margin-left:5rem;margin-right:5rem;width:calc(100% - 5rem)}.ui.celled.grid{width:100%;margin:1em 0;box-shadow:0 0 0 1px #D4D4D5}.ui.celled.grid>.row{width:100%!important;margin:0;padding:0;box-shadow:0 -1px 0 0 #D4D4D5}.ui.celled.grid>.column:not(.row),.ui.celled.grid>.row>.column{box-shadow:-1px 0 0 0 #D4D4D5;padding:1em}.ui.celled.grid>.column:first-child,.ui.celled.grid>.row>.column:first-child{box-shadow:none}.ui.relaxed.celled.grid>.column:not(.row),.ui.relaxed.celled.grid>.row>.column{padding:1.5em}.ui[class*="very relaxed"].celled.grid>.column:not(.row),.ui[class*="very relaxed"].celled.grid>.row>.column{padding:2em}.ui[class*="internally celled"].grid{box-shadow:none;margin:0}.ui[class*="internally celled"].grid>.row:first-child,.ui[class*="internally celled"].grid>.row>.column:first-child{box-shadow:none}.ui.grid>.row>[class*="top aligned"].column,.ui.grid>[class*="top aligned"].column:not(.row),.ui.grid>[class*="top aligned"].row>.column,.ui[class*="top aligned"].grid>.column:not(.row),.ui[class*="top aligned"].grid>.row>.column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;vertical-align:top;-webkit-align-self:flex-start!important;-ms-flex-item-align:start!important;align-self:flex-start!important}.ui.grid>.row>[class*="middle aligned"].column,.ui.grid>[class*="middle aligned"].column:not(.row),.ui.grid>[class*="middle aligned"].row>.column,.ui[class*="middle aligned"].grid>.column:not(.row),.ui[class*="middle aligned"].grid>.row>.column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;vertical-align:middle;-webkit-align-self:center!important;-ms-flex-item-align:center!important;align-self:center!important}.ui.grid>.row>[class*="bottom aligned"].column,.ui.grid>[class*="bottom aligned"].column:not(.row),.ui.grid>[class*="bottom aligned"].row>.column,.ui[class*="bottom aligned"].grid>.column:not(.row),.ui[class*="bottom aligned"].grid>.row>.column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;vertical-align:bottom;-webkit-align-self:flex-end!important;-ms-flex-item-align:end!important;align-self:flex-end!important}.ui.grid>.row>.stretched.column,.ui.grid>.stretched.column:not(.row),.ui.grid>.stretched.row>.column,.ui.stretched.grid>.column,.ui.stretched.grid>.row>.column{display:-webkit-inline-box!important;display:-webkit-inline-flex!important;display:-ms-inline-flexbox!important;display:inline-flex!important;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.ui.grid>.row>.stretched.column>*,.ui.grid>.stretched.column:not(.row)>*,.ui.grid>.stretched.row>.column>*,.ui.stretched.grid>.column>*,.ui.stretched.grid>.row>.column>*{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.ui.grid>.row>[class*="left aligned"].column.column,.ui.grid>[class*="left aligned"].column.column,.ui.grid>[class*="left aligned"].row>.column,.ui[class*="left aligned"].grid>.column,.ui[class*="left aligned"].grid>.row>.column{text-align:left;-webkit-align-self:inherit;-ms-flex-item-align:inherit;align-self:inherit}.ui.grid>.row>[class*="center aligned"].column.column,.ui.grid>[class*="center aligned"].column.column,.ui.grid>[class*="center aligned"].row>.column,.ui[class*="center aligned"].grid>.column,.ui[class*="center aligned"].grid>.row>.column{text-align:center;-webkit-align-self:inherit;-ms-flex-item-align:inherit;align-self:inherit}.ui[class*="center aligned"].grid{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.ui.grid>.row>[class*="right aligned"].column.column,.ui.grid>[class*="right aligned"].column.column,.ui.grid>[class*="right aligned"].row>.column,.ui[class*="right aligned"].grid>.column,.ui[class*="right aligned"].grid>.row>.column{text-align:right;-webkit-align-self:inherit;-ms-flex-item-align:inherit;align-self:inherit}.ui.grid>.justified.column.column,.ui.grid>.justified.row>.column,.ui.grid>.row>.justified.column.column,.ui.justified.grid>.column,.ui.justified.grid>.row>.column{text-align:justify;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}.ui.grid>.row>.black.column,.ui.grid>.row>.blue.column,.ui.grid>.row>.brown.column,.ui.grid>.row>.green.column,.ui.grid>.row>.grey.column,.ui.grid>.row>.olive.column,.ui.grid>.row>.orange.column,.ui.grid>.row>.pink.column,.ui.grid>.row>.purple.column,.ui.grid>.row>.red.column,.ui.grid>.row>.teal.column,.ui.grid>.row>.violet.column,.ui.grid>.row>.yellow.column{margin-top:-1rem;margin-bottom:-1rem;padding-top:1rem;padding-bottom:1rem}.ui.grid>.red.column,.ui.grid>.red.row,.ui.grid>.row>.red.column{background-color:#DB2828!important;color:#FFF}.ui.grid>.orange.column,.ui.grid>.orange.row,.ui.grid>.row>.orange.column{background-color:#F2711C!important;color:#FFF}.ui.grid>.row>.yellow.column,.ui.grid>.yellow.column,.ui.grid>.yellow.row{background-color:#FBBD08!important;color:#FFF}.ui.grid>.olive.column,.ui.grid>.olive.row,.ui.grid>.row>.olive.column{background-color:#B5CC18!important;color:#FFF}.ui.grid>.green.column,.ui.grid>.green.row,.ui.grid>.row>.green.column{background-color:#21BA45!important;color:#FFF}.ui.grid>.row>.teal.column,.ui.grid>.teal.column,.ui.grid>.teal.row{background-color:#00B5AD!important;color:#FFF}.ui.grid>.blue.column,.ui.grid>.blue.row,.ui.grid>.row>.blue.column{background-color:#2185D0!important;color:#FFF}.ui.grid>.row>.violet.column,.ui.grid>.violet.column,.ui.grid>.violet.row{background-color:#6435C9!important;color:#FFF}.ui.grid>.purple.column,.ui.grid>.purple.row,.ui.grid>.row>.purple.column{background-color:#A333C8!important;color:#FFF}.ui.grid>.pink.column,.ui.grid>.pink.row,.ui.grid>.row>.pink.column{background-color:#E03997!important;color:#FFF}.ui.grid>.brown.column,.ui.grid>.brown.row,.ui.grid>.row>.brown.column{background-color:#A5673F!important;color:#FFF}.ui.grid>.grey.column,.ui.grid>.grey.row,.ui.grid>.row>.grey.column{background-color:#767676!important;color:#FFF}.ui.grid>.black.column,.ui.grid>.black.row,.ui.grid>.row>.black.column{background-color:#1B1C1D!important;color:#FFF}.ui.grid>[class*="equal width"].row>.column,.ui[class*="equal width"].grid>.column:not(.row),.ui[class*="equal width"].grid>.row>.column{display:inline-block;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.ui.grid>[class*="equal width"].row>.wide.column,.ui[class*="equal width"].grid>.row>.wide.column,.ui[class*="equal width"].grid>.wide.column{-webkit-box-flex:0;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0}@media only screen and (max-width:767px){.ui.grid>[class*="mobile reversed"].row,.ui[class*="mobile reversed"].grid,.ui[class*="mobile reversed"].grid>.row{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.ui.stackable[class*="mobile reversed"],.ui[class*="mobile vertically reversed"].grid{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.ui[class*="mobile reversed"].divided.grid:not([class*="vertically divided"])>.column:first-child,.ui[class*="mobile reversed"].divided.grid:not([class*="vertically divided"])>.row>.column:first-child{box-shadow:-1px 0 0 0 rgba(34,36,38,.15)}.ui[class*="mobile reversed"].divided.grid:not([class*="vertically divided"])>.column:last-child,.ui[class*="mobile reversed"].divided.grid:not([class*="vertically divided"])>.row>.column:last-child{box-shadow:none}.ui.grid[class*="vertically divided"][class*="mobile vertically reversed"]>.row:first-child:before{box-shadow:0 -1px 0 0 rgba(34,36,38,.15)}.ui.grid[class*="vertically divided"][class*="mobile vertically reversed"]>.row:last-child:before{box-shadow:none}.ui[class*="mobile reversed"].celled.grid>.row>.column:first-child{box-shadow:-1px 0 0 0 #D4D4D5}.ui[class*="mobile reversed"].celled.grid>.row>.column:last-child{box-shadow:none}}@media only screen and (min-width:768px) and (max-width:991px){.ui.grid>[class*="tablet reversed"].row,.ui[class*="tablet reversed"].grid,.ui[class*="tablet reversed"].grid>.row{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.ui[class*="tablet vertically reversed"].grid{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.ui[class*="tablet reversed"].divided.grid:not([class*="vertically divided"])>.column:first-child,.ui[class*="tablet reversed"].divided.grid:not([class*="vertically divided"])>.row>.column:first-child{box-shadow:-1px 0 0 0 rgba(34,36,38,.15)}.ui[class*="tablet reversed"].divided.grid:not([class*="vertically divided"])>.column:last-child,.ui[class*="tablet reversed"].divided.grid:not([class*="vertically divided"])>.row>.column:last-child{box-shadow:none}.ui.grid[class*="vertically divided"][class*="tablet vertically reversed"]>.row:first-child:before{box-shadow:0 -1px 0 0 rgba(34,36,38,.15)}.ui.grid[class*="vertically divided"][class*="tablet vertically reversed"]>.row:last-child:before{box-shadow:none}.ui[class*="tablet reversed"].celled.grid>.row>.column:first-child{box-shadow:-1px 0 0 0 #D4D4D5}.ui[class*="tablet reversed"].celled.grid>.row>.column:last-child{box-shadow:none}}@media only screen and (min-width:992px){.ui.grid>[class*="computer reversed"].row,.ui[class*="computer reversed"].grid,.ui[class*="computer reversed"].grid>.row{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.ui[class*="computer vertically reversed"].grid{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.ui[class*="computer reversed"].divided.grid:not([class*="vertically divided"])>.column:first-child,.ui[class*="computer reversed"].divided.grid:not([class*="vertically divided"])>.row>.column:first-child{box-shadow:-1px 0 0 0 rgba(34,36,38,.15)}.ui[class*="computer reversed"].divided.grid:not([class*="vertically divided"])>.column:last-child,.ui[class*="computer reversed"].divided.grid:not([class*="vertically divided"])>.row>.column:last-child{box-shadow:none}.ui.grid[class*="vertically divided"][class*="computer vertically reversed"]>.row:first-child:before{box-shadow:0 -1px 0 0 rgba(34,36,38,.15)}.ui.grid[class*="vertically divided"][class*="computer vertically reversed"]>.row:last-child:before{box-shadow:none}.ui[class*="computer reversed"].celled.grid>.row>.column:first-child{box-shadow:-1px 0 0 0 #D4D4D5}.ui[class*="computer reversed"].celled.grid>.row>.column:last-child{box-shadow:none}}@media only screen and (min-width:768px) and (max-width:991px){.ui.doubling.grid{width:auto}.ui.doubling.grid>.row,.ui.grid>.doubling.row{margin:0!important;padding:0!important}.ui.doubling.grid>.row>.column,.ui.grid>.doubling.row>.column{display:inline-block!important;padding-top:1rem!important;padding-bottom:1rem!important;box-shadow:none!important;margin:0}.ui.grid>[class*="two column"].doubling.row.row>.column,.ui[class*="two column"].doubling.grid>.column:not(.row),.ui[class*="two column"].doubling.grid>.row>.column{width:100%!important}.ui.grid>[class*="three column"].doubling.row.row>.column,.ui.grid>[class*="four column"].doubling.row.row>.column,.ui[class*="three column"].doubling.grid>.column:not(.row),.ui[class*="three column"].doubling.grid>.row>.column,.ui[class*="four column"].doubling.grid>.column:not(.row),.ui[class*="four column"].doubling.grid>.row>.column{width:50%!important}.ui.grid>[class*="five column"].doubling.row.row>.column,.ui.grid>[class*="six column"].doubling.row.row>.column,.ui.grid>[class*="seven column"].doubling.row.row>.column,.ui[class*="five column"].doubling.grid>.column:not(.row),.ui[class*="five column"].doubling.grid>.row>.column,.ui[class*="six column"].doubling.grid>.column:not(.row),.ui[class*="six column"].doubling.grid>.row>.column,.ui[class*="seven column"].doubling.grid>.column:not(.row),.ui[class*="seven column"].doubling.grid>.row>.column{width:33.33333333%!important}.ui.grid>[class*="eight column"].doubling.row.row>.column,.ui.grid>[class*="nine column"].doubling.row.row>.column,.ui[class*="eight column"].doubling.grid>.column:not(.row),.ui[class*="eight column"].doubling.grid>.row>.column,.ui[class*="nine column"].doubling.grid>.column:not(.row),.ui[class*="nine column"].doubling.grid>.row>.column{width:25%!important}.ui.grid>[class*="ten column"].doubling.row.row>.column,.ui.grid>[class*="eleven column"].doubling.row.row>.column,.ui[class*="ten column"].doubling.grid>.column:not(.row),.ui[class*="ten column"].doubling.grid>.row>.column,.ui[class*="eleven column"].doubling.grid>.column:not(.row),.ui[class*="eleven column"].doubling.grid>.row>.column{width:20%!important}.ui.grid>[class*="twelve column"].doubling.row.row>.column,.ui.grid>[class*="thirteen column"].doubling.row.row>.column,.ui[class*="twelve column"].doubling.grid>.column:not(.row),.ui[class*="twelve column"].doubling.grid>.row>.column,.ui[class*="thirteen column"].doubling.grid>.column:not(.row),.ui[class*="thirteen column"].doubling.grid>.row>.column{width:16.66666667%!important}.ui.grid>[class*="fourteen column"].doubling.row.row>.column,.ui.grid>[class*="fifteen column"].doubling.row.row>.column,.ui[class*="fourteen column"].doubling.grid>.column:not(.row),.ui[class*="fourteen column"].doubling.grid>.row>.column,.ui[class*="fifteen column"].doubling.grid>.column:not(.row),.ui[class*="fifteen column"].doubling.grid>.row>.column{width:14.28571429%!important}.ui.grid>[class*="sixteen column"].doubling.row.row>.column,.ui[class*="sixteen column"].doubling.grid>.column:not(.row),.ui[class*="sixteen column"].doubling.grid>.row>.column{width:12.5%!important}.ui.grid.grid.grid>.row>[class*="computer only"].column:not(.tablet),.ui.grid.grid.grid>.row>[class*="large screen only"].column:not(.mobile),.ui.grid.grid.grid>.row>[class*="widescreen only"].column:not(.mobile),.ui.grid.grid.grid>.row>[class*="mobile only"].column:not(.tablet),.ui.grid.grid.grid>[class*="computer only"].column:not(.tablet),.ui.grid.grid.grid>[class*="computer only"].row:not(.tablet),.ui.grid.grid.grid>[class*="large screen only"].column:not(.mobile),.ui.grid.grid.grid>[class*="large screen only"].row:not(.mobile),.ui.grid.grid.grid>[class*="widescreen only"].column:not(.mobile),.ui.grid.grid.grid>[class*="widescreen only"].row:not(.mobile),.ui.grid.grid.grid>[class*="mobile only"].column:not(.tablet),.ui.grid.grid.grid>[class*="mobile only"].row:not(.tablet),.ui[class*="computer only"].grid.grid.grid:not(.tablet),.ui[class*="large screen only"].grid.grid.grid:not(.mobile),.ui[class*="widescreen only"].grid.grid.grid:not(.mobile),.ui[class*="mobile only"].grid.grid.grid:not(.tablet){display:none!important}}@media only screen and (max-width:767px){.ui.doubling.grid>.row,.ui.grid>.doubling.row{margin:0!important;padding:0!important}.ui.doubling.grid>.row>.column,.ui.grid>.doubling.row>.column{padding-top:1rem!important;padding-bottom:1rem!important;margin:0!important;box-shadow:none!important}.ui.grid>[class*="two column"].doubling:not(.stackable).row.row>.column,.ui[class*="two column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="two column"].doubling:not(.stackable).grid>.row>.column{width:100%!important}.ui.grid>[class*="three column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="four column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="five column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="six column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="seven column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="eight column"].doubling:not(.stackable).row.row>.column,.ui[class*="three column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="three column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="four column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="four column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="five column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="five column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="six column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="six column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="seven column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="seven column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="eight column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="eight column"].doubling:not(.stackable).grid>.row>.column{width:50%!important}.ui.grid>[class*="nine column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="ten column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="eleven column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="twelve column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="thirteen column"].doubling:not(.stackable).row.row>.column,.ui[class*="nine column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="nine column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="ten column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="ten column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="eleven column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="eleven column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="twelve column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="twelve column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="thirteen column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="thirteen column"].doubling:not(.stackable).grid>.row>.column{width:33.33333333%!important}.ui.grid>[class*="fourteen column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="fifteen column"].doubling:not(.stackable).row.row>.column,.ui.grid>[class*="sixteen column"].doubling:not(.stackable).row.row>.column,.ui[class*="fourteen column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="fourteen column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="fifteen column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="fifteen column"].doubling:not(.stackable).grid>.row>.column,.ui[class*="sixteen column"].doubling:not(.stackable).grid>.column:not(.row),.ui[class*="sixteen column"].doubling:not(.stackable).grid>.row>.column{width:25%!important}.ui.stackable.grid{width:auto;margin-left:0!important;margin-right:0!important}.ui.grid>.stackable.stackable.row>.column,.ui.stackable.grid>.column.grid>.column,.ui.stackable.grid>.column.row>.column,.ui.stackable.grid>.column:not(.row),.ui.stackable.grid>.row>.column,.ui.stackable.grid>.row>.wide.column,.ui.stackable.grid>.wide.column{width:100%!important;margin:0!important;box-shadow:none!important;padding:1rem!important}.ui.stackable.grid:not(.vertically)>.row{margin:0;padding:0}.ui.container>.ui.stackable.grid>.column,.ui.container>.ui.stackable.grid>.row>.column{padding-left:0!important;padding-right:0!important}.ui.grid .ui.stackable.grid,.ui.segment:not(.vertical) .ui.stackable.page.grid{margin-left:-1rem!important;margin-right:-1rem!important}.ui.stackable.celled.grid>.column:not(.row):first-child,.ui.stackable.celled.grid>.row:first-child>.column:first-child,.ui.stackable.divided.grid>.column:not(.row):first-child,.ui.stackable.divided.grid>.row:first-child>.column:first-child{border-top:none!important}.ui.inverted.stackable.celled.grid>.column:not(.row),.ui.inverted.stackable.celled.grid>.row>.column,.ui.inverted.stackable.divided.grid>.column:not(.row),.ui.inverted.stackable.divided.grid>.row>.column{border-top:1px solid rgba(255,255,255,.1)}.ui.stackable.celled.grid>.column:not(.row),.ui.stackable.celled.grid>.row>.column,.ui.stackable.divided:not(.vertically).grid>.column:not(.row),.ui.stackable.divided:not(.vertically).grid>.row>.column{border-top:1px solid rgba(34,36,38,.15);box-shadow:none!important;padding-top:2rem!important;padding-bottom:2rem!important}.ui.stackable.celled.grid>.row{box-shadow:none!important}.ui.stackable.divided:not(.vertically).grid>.column:not(.row),.ui.stackable.divided:not(.vertically).grid>.row>.column{padding-left:0!important;padding-right:0!important}.ui.grid.grid.grid>.row>[class*="tablet only"].column:not(.mobile),.ui.grid.grid.grid>.row>[class*="computer only"].column:not(.mobile),.ui.grid.grid.grid>.row>[class*="large screen only"].column:not(.mobile),.ui.grid.grid.grid>.row>[class*="widescreen only"].column:not(.mobile),.ui.grid.grid.grid>[class*="tablet only"].column:not(.mobile),.ui.grid.grid.grid>[class*="tablet only"].row:not(.mobile),.ui.grid.grid.grid>[class*="computer only"].column:not(.mobile),.ui.grid.grid.grid>[class*="computer only"].row:not(.mobile),.ui.grid.grid.grid>[class*="large screen only"].column:not(.mobile),.ui.grid.grid.grid>[class*="large screen only"].row:not(.mobile),.ui.grid.grid.grid>[class*="widescreen only"].column:not(.mobile),.ui.grid.grid.grid>[class*="widescreen only"].row:not(.mobile),.ui[class*="tablet only"].grid.grid.grid:not(.mobile),.ui[class*="computer only"].grid.grid.grid:not(.mobile),.ui[class*="large screen only"].grid.grid.grid:not(.mobile),.ui[class*="widescreen only"].grid.grid.grid:not(.mobile){display:none!important}}@media only screen and (min-width:992px) and (max-width:1199px){.ui.grid.grid.grid>.row>[class*="tablet only"].column:not(.computer),.ui.grid.grid.grid>.row>[class*="large screen only"].column:not(.mobile),.ui.grid.grid.grid>.row>[class*="widescreen only"].column:not(.mobile),.ui.grid.grid.grid>.row>[class*="mobile only"].column:not(.computer),.ui.grid.grid.grid>[class*="tablet only"].column:not(.computer),.ui.grid.grid.grid>[class*="tablet only"].row:not(.computer),.ui.grid.grid.grid>[class*="large screen only"].column:not(.mobile),.ui.grid.grid.grid>[class*="large screen only"].row:not(.mobile),.ui.grid.grid.grid>[class*="widescreen only"].column:not(.mobile),.ui.grid.grid.grid>[class*="widescreen only"].row:not(.mobile),.ui.grid.grid.grid>[class*="mobile only"].column:not(.computer),.ui.grid.grid.grid>[class*="mobile only"].row:not(.computer),.ui[class*="tablet only"].grid.grid.grid:not(.computer),.ui[class*="large screen only"].grid.grid.grid:not(.mobile),.ui[class*="widescreen only"].grid.grid.grid:not(.mobile),.ui[class*="mobile only"].grid.grid.grid:not(.computer){display:none!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.ui.grid.grid.grid>.row>[class*="tablet only"].column:not(.computer),.ui.grid.grid.grid>.row>[class*="widescreen only"].column:not(.mobile),.ui.grid.grid.grid>.row>[class*="mobile only"].column:not(.computer),.ui.grid.grid.grid>[class*="tablet only"].column:not(.computer),.ui.grid.grid.grid>[class*="tablet only"].row:not(.computer),.ui.grid.grid.grid>[class*="widescreen only"].column:not(.mobile),.ui.grid.grid.grid>[class*="widescreen only"].row:not(.mobile),.ui.grid.grid.grid>[class*="mobile only"].column:not(.computer),.ui.grid.grid.grid>[class*="mobile only"].row:not(.computer),.ui[class*="tablet only"].grid.grid.grid:not(.computer),.ui[class*="widescreen only"].grid.grid.grid:not(.mobile),.ui[class*="mobile only"].grid.grid.grid:not(.computer){display:none!important}}@media only screen and (min-width:1920px){.ui.grid.grid.grid>.row>[class*="tablet only"].column:not(.computer),.ui.grid.grid.grid>.row>[class*="mobile only"].column:not(.computer),.ui.grid.grid.grid>[class*="tablet only"].column:not(.computer),.ui.grid.grid.grid>[class*="tablet only"].row:not(.computer),.ui.grid.grid.grid>[class*="mobile only"].column:not(.computer),.ui.grid.grid.grid>[class*="mobile only"].row:not(.computer),.ui[class*="tablet only"].grid.grid.grid:not(.computer),.ui[class*="mobile only"].grid.grid.grid:not(.computer){display:none!important}}.ui.menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin:1rem 0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;background:#FFF;font-weight:400;border:1px solid rgba(34,36,38,.15);box-shadow:0 1px 2px 0 rgba(34,36,38,.15);border-radius:.28571429rem;min-height:2.85714286em}.ui.menu:after{content:'';display:block;height:0;clear:both;visibility:hidden}.ui.menu:first-child{margin-top:0}.ui.menu:last-child{margin-bottom:0}.ui.menu .menu{margin:0}.ui.menu:not(.vertical)>.menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.ui.menu:not(.vertical) .item{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.ui.menu .item{position:relative;vertical-align:middle;line-height:1;text-decoration:none;-webkit-tap-highlight-color:transparent;-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:0 0;padding:.92857143em 1.14285714em;text-transform:none;color:rgba(0,0,0,.87);font-weight:400;-webkit-transition:background .1s ease,box-shadow .1s ease,color .1s ease;transition:background .1s ease,box-shadow .1s ease,color .1s ease}.ui.menu>.item:first-child{border-radius:.28571429rem 0 0 .28571429rem}.ui.menu .item:before{position:absolute;content:'';top:0;right:0;height:100%;width:1px;background:rgba(34,36,38,.1)}.ui.menu .item>a:not(.ui),.ui.menu .item>p:only-child,.ui.menu .text.item>*{-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;line-height:1.3}.ui.menu .item>p:first-child{margin-top:0}.ui.menu .item>p:last-child{margin-bottom:0}.ui.menu .item>i.icon{opacity:.9;float:none;margin:0 .35714286em 0 0}.ui.menu:not(.vertical) .item>.button{position:relative;top:0;margin:-.5em 0;padding-bottom:.78571429em;padding-top:.78571429em;font-size:1em}.ui.menu>.container,.ui.menu>.grid{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:inherit;-webkit-align-items:inherit;-ms-flex-align:inherit;align-items:inherit;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:inherit;-ms-flex-direction:inherit;flex-direction:inherit}.ui.menu .item>.input{width:100%}.ui.menu:not(.vertical) .item>.input{position:relative;top:0;margin:-.5em 0}.ui.menu .item>.input input{font-size:1em;padding-top:.57142857em;padding-bottom:.57142857em}.ui.menu .header.item,.ui.vertical.menu .header.item{margin:0;background:0 0;text-transform:normal;font-weight:700}.ui.vertical.menu .item>.header:not(.ui){margin:0 0 .5em;font-size:1em;font-weight:700}.ui.menu .item>i.dropdown.icon{padding:0;float:right;margin:0 0 0 1em}.ui.menu .dropdown.item .menu{left:0;min-width:calc(100% - 1px);border-radius:0 0 .28571429rem .28571429rem;background:#FFF;margin:0;box-shadow:0 1px 3px 0 rgba(0,0,0,.08);-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-webkit-flex-direction:column!important;-ms-flex-direction:column!important;flex-direction:column!important}.ui.menu .ui.dropdown .menu>.item{margin:0;text-align:left;font-size:1em!important;padding:.78571429em 1.14285714em!important;background:0 0!important;color:rgba(0,0,0,.87)!important;text-transform:none!important;font-weight:400!important;box-shadow:none!important;-webkit-transition:none!important;transition:none!important}.ui.menu .ui.dropdown .menu>.item:hover,.ui.menu .ui.dropdown .menu>.selected.item{background:rgba(0,0,0,.05)!important;color:rgba(0,0,0,.95)!important}.ui.menu .ui.dropdown .menu>.active.item{background:rgba(0,0,0,.03)!important;font-weight:700!important;color:rgba(0,0,0,.95)!important}.ui.menu .ui.dropdown.item .menu .item:not(.filtered){display:block}.ui.menu .ui.dropdown .menu>.item .icon:not(.dropdown){display:inline-block;font-size:1em!important;float:none;margin:0 .75em 0 0}.ui.secondary.menu .dropdown.item>.menu,.ui.text.menu .dropdown.item>.menu{border-radius:.28571429rem;margin-top:.35714286em}.ui.menu .pointing.dropdown.item .menu{margin-top:.75em}.ui.inverted.menu .search.dropdown.item>.search,.ui.inverted.menu .search.dropdown.item>.text{color:rgba(255,255,255,.9)}.ui.vertical.menu .dropdown.item>.icon{float:right;content:"\f0da";margin-left:1em}.ui.vertical.menu .dropdown.item .menu{left:100%;min-width:0;margin:0;box-shadow:0 1px 3px 0 rgba(0,0,0,.08);border-radius:0 .28571429rem .28571429rem}.ui.vertical.menu .dropdown.item.upward .menu{bottom:0}.ui.vertical.menu .dropdown.item:not(.upward) .menu{top:0}.ui.vertical.menu .active.dropdown.item{border-top-right-radius:0;border-bottom-right-radius:0}.ui.vertical.menu .dropdown.active.item{box-shadow:none}.ui.item.menu .dropdown .menu .item{width:100%}.ui.menu .item>.label{background:#999;color:#FFF;margin-left:1em;padding:.3em .78571429em}.ui.vertical.menu .item>.label{background:#999;color:#FFF;margin-top:-.15em;margin-bottom:-.15em;padding:.3em .78571429em;float:right;text-align:center}.ui.menu .item>.floating.label{padding:.3em .78571429em}.ui.menu .item>img:not(.ui){display:inline-block;vertical-align:middle;margin:-.3em 0;width:2.5em}.ui.vertical.menu .item>img:not(.ui):only-child{display:block;max-width:100%;width:auto}.ui.vertical.sidebar.menu>.item:first-child:before{display:block!important}.ui.vertical.sidebar.menu>.item::before{top:auto;bottom:0}@media only screen and (max-width:767px){.ui.menu>.ui.container{width:100%!important;margin-left:0!important;margin-right:0!important}}@media only screen and (min-width:768px){.ui.menu:not(.secondary):not(.text):not(.tabular):not(.borderless)>.container>.item:not(.right):not(.borderless):first-child{border-left:1px solid rgba(34,36,38,.1)}}.ui.link.menu .item:hover,.ui.menu .dropdown.item:hover,.ui.menu .link.item:hover,.ui.menu a.item:hover{cursor:pointer;background:rgba(0,0,0,.03);color:rgba(0,0,0,.95)}.ui.link.menu .item:active,.ui.menu .link.item:active,.ui.menu a.item:active{background:rgba(0,0,0,.03);color:rgba(0,0,0,.95)}.ui.menu .active.item{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95);font-weight:400;box-shadow:none}.ui.menu .active.item>i.icon{opacity:1}.ui.menu .active.item:hover,.ui.vertical.menu .active.item:hover{background-color:rgba(0,0,0,.05);color:rgba(0,0,0,.95)}.ui.menu .item.disabled,.ui.menu .item.disabled:hover{cursor:default;background-color:transparent!important;color:rgba(40,40,40,.3)}.ui.menu:not(.vertical) .left.item,.ui.menu:not(.vertical) .left.menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-right:auto!important}.ui.menu:not(.vertical) .right.item,.ui.menu:not(.vertical) .right.menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-left:auto!important}.ui.menu .right.item::before,.ui.menu .right.menu>.item::before{right:auto;left:0}.ui.vertical.menu{display:block;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;background:#FFF;box-shadow:0 1px 2px 0 rgba(34,36,38,.15)}.ui.vertical.menu .item{display:block;background:0 0;border-top:none;border-right:none}.ui.vertical.menu>.item:first-child{border-radius:.28571429rem .28571429rem 0 0}.ui.vertical.menu>.item:last-child{border-radius:0 0 .28571429rem .28571429rem}.ui.vertical.menu .item>i.icon{width:1.18em;float:right;margin:0 0 0 .5em}.ui.vertical.menu .item>.label+i.icon{float:none;margin:0 .5em 0 0}.ui.vertical.menu .item:before{position:absolute;content:'';top:0;left:0;width:100%;height:1px;background:rgba(34,36,38,.1)}.ui.vertical.menu .item:first-child:before{display:none!important}.ui.vertical.menu .item>.menu{margin:.5em -1.14285714em 0}.ui.vertical.menu .menu .item{background:0 0;padding:.5em 1.33333333em;font-size:.85714286em;color:rgba(0,0,0,.5)}.ui.vertical.menu .item .menu .link.item:hover,.ui.vertical.menu .item .menu a.item:hover{color:rgba(0,0,0,.85)}.ui.vertical.menu .menu .item:before{display:none}.ui.vertical.menu .active.item{background:rgba(0,0,0,.05);border-radius:0;box-shadow:none}.ui.vertical.menu>.active.item:first-child{border-radius:.28571429rem .28571429rem 0 0}.ui.vertical.menu>.active.item:last-child{border-radius:0 0 .28571429rem .28571429rem}.ui.vertical.menu>.active.item:only-child{border-radius:.28571429rem}.ui.vertical.menu .active.item .menu .active.item{border-left:none}.ui.vertical.menu .item .menu .active.item{background-color:transparent;font-weight:700;color:rgba(0,0,0,.95)}.ui.tabular.menu{border-radius:0;box-shadow:none!important;border:none;background:none;border-bottom:1px solid #D4D4D5}.ui.tabular.fluid.menu{width:calc(100% + 2px)!important}.ui.tabular.menu .item{background:0 0;border-bottom:none;border-left:1px solid transparent;border-right:1px solid transparent;border-top:2px solid transparent;padding:.92857143em 1.42857143em;color:rgba(0,0,0,.87)}.ui.tabular.menu .item:before{display:none}.ui.tabular.menu .item:hover{background-color:transparent;color:rgba(0,0,0,.8)}.ui.tabular.menu .active.item{background:#FFF;color:rgba(0,0,0,.95);border-top-width:1px;border-color:#D4D4D5;font-weight:700;margin-bottom:-1px;box-shadow:none;border-radius:.28571429rem .28571429rem 0 0!important}.ui.tabular.menu+.attached:not(.top).segment,.ui.tabular.menu+.attached:not(.top).segment+.attached:not(.top).segment{border-top:none;margin-left:0;margin-top:0;margin-right:0;width:100%}.top.attached.segment+.ui.bottom.tabular.menu{position:relative;width:calc(100% + 2px);left:-1px}.ui.bottom.tabular.menu{background:none;border-radius:0;box-shadow:none!important;border-bottom:none;border-top:1px solid #D4D4D5}.ui.bottom.tabular.menu .item{background:0 0;border-left:1px solid transparent;border-right:1px solid transparent;border-bottom:1px solid transparent;border-top:none}.ui.bottom.tabular.menu .active.item{background:#FFF;color:rgba(0,0,0,.95);border-color:#D4D4D5;margin:-1px 0 0;border-radius:0 0 .28571429rem .28571429rem!important}.ui.vertical.tabular.menu{background:none;border-radius:0;box-shadow:none!important;border-bottom:none;border-right:1px solid #D4D4D5}.ui.vertical.tabular.menu .item{background:0 0;border-left:1px solid transparent;border-bottom:1px solid transparent;border-top:1px solid transparent;border-right:none}.ui.vertical.tabular.menu .active.item{background:#FFF;color:rgba(0,0,0,.95);border-color:#D4D4D5;margin:0 -1px 0 0;border-radius:.28571429rem 0 0 .28571429rem!important}.ui.vertical.right.tabular.menu{background:none;border-radius:0;box-shadow:none!important;border-bottom:none;border-right:none;border-left:1px solid #D4D4D5}.ui.vertical.right.tabular.menu .item{background:0 0;border-right:1px solid transparent;border-bottom:1px solid transparent;border-top:1px solid transparent;border-left:none}.ui.vertical.right.tabular.menu .active.item{background:#FFF;color:rgba(0,0,0,.95);border-color:#D4D4D5;margin:0 0 0 -1px;border-radius:0 .28571429rem .28571429rem 0!important}.ui.tabular.menu .active.dropdown.item{margin-bottom:0;border-left:1px solid transparent;border-right:1px solid transparent;border-top:2px solid transparent;border-bottom:none}.ui.pagination.menu{margin:0;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.ui.pagination.menu .item:last-child{border-radius:0 .28571429rem .28571429rem 0}.ui.pagination.menu .item:last-child:before{display:none}.ui.pagination.menu .item{min-width:3em;text-align:center}.ui.pagination.menu .icon.item i.icon{vertical-align:top}.ui.pagination.menu .active.item{border-top:none;padding-top:.92857143em;background-color:rgba(0,0,0,.05);color:rgba(0,0,0,.95);box-shadow:none}.ui.secondary.menu{background:0 0;margin-left:-.35714286em;margin-right:-.35714286em;border-radius:0;border:none;box-shadow:none}.ui.secondary.menu .item{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;box-shadow:none;border:none;padding:.78571429em .92857143em;margin:0 .35714286em;background:0 0;-webkit-transition:color .1s ease;transition:color .1s ease;border-radius:.28571429rem}.ui.secondary.menu .item:before{display:none!important}.ui.secondary.menu .header.item{border-radius:0;border-right:none;background:none}.ui.secondary.menu .item>img:not(.ui){margin:0}.ui.secondary.menu .dropdown.item:hover,.ui.secondary.menu .link.item:hover,.ui.secondary.menu a.item:hover{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95)}.ui.secondary.menu .active.item{box-shadow:none;background:rgba(0,0,0,.05);color:rgba(0,0,0,.95);border-radius:.28571429rem}.ui.secondary.menu .active.item:hover{box-shadow:none;background:rgba(0,0,0,.05);color:rgba(0,0,0,.95)}.ui.secondary.inverted.menu .link.item,.ui.secondary.inverted.menu a.item{color:rgba(255,255,255,.7)!important}.ui.secondary.inverted.menu .dropdown.item:hover,.ui.secondary.inverted.menu .link.item:hover,.ui.secondary.inverted.menu a.item:hover{background:rgba(255,255,255,.08);color:#fff!important}.ui.secondary.inverted.menu .active.item{background:rgba(255,255,255,.15);color:#fff!important}.ui.secondary.item.menu{margin-left:0;margin-right:0}.ui.secondary.item.menu .item:last-child{margin-right:0}.ui.secondary.attached.menu{box-shadow:none}.ui.vertical.secondary.menu .item:not(.dropdown)>.menu{margin:0 -.92857143em}.ui.vertical.secondary.menu .item:not(.dropdown)>.menu>.item{margin:0;padding:.5em 1.33333333em}.ui.secondary.vertical.menu>.item{border:none;margin:0 0 .35714286em;border-radius:.28571429rem!important}.ui.secondary.vertical.menu>.header.item{border-radius:0}.ui.secondary.inverted.menu,.ui.vertical.secondary.menu .item>.menu .item{background-color:transparent}.ui.secondary.pointing.menu{margin-left:0;margin-right:0;border-bottom:2px solid rgba(34,36,38,.15)}.ui.secondary.pointing.menu .item{border-bottom-color:transparent;border-bottom-style:solid;border-radius:0;-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;margin:0 0 -2px;padding:.85714286em 1.14285714em;border-bottom-width:2px;-webkit-transition:color .1s ease;transition:color .1s ease}.ui.secondary.pointing.menu .header.item{color:rgba(0,0,0,.85)!important}.ui.secondary.pointing.menu .text.item{box-shadow:none!important}.ui.secondary.pointing.menu .item:after{display:none}.ui.secondary.pointing.menu .dropdown.item:hover,.ui.secondary.pointing.menu .link.item:hover,.ui.secondary.pointing.menu a.item:hover{background-color:transparent;color:rgba(0,0,0,.87)}.ui.secondary.pointing.menu .dropdown.item:active,.ui.secondary.pointing.menu .link.item:active,.ui.secondary.pointing.menu a.item:active{background-color:transparent;border-color:rgba(34,36,38,.15)}.ui.secondary.pointing.menu .active.item{background-color:transparent;box-shadow:none;border-color:#1B1C1D;font-weight:700;color:rgba(0,0,0,.95)}.ui.secondary.pointing.menu .active.item:hover{border-color:#1B1C1D;color:rgba(0,0,0,.95)}.ui.secondary.pointing.menu .active.dropdown.item{border-color:transparent}.ui.secondary.vertical.pointing.menu{border-bottom-width:0;border-right-width:2px;border-right-style:solid;border-right-color:rgba(34,36,38,.15)}.ui.secondary.vertical.pointing.menu .item{border-bottom:none;border-right-style:solid;border-right-color:transparent;border-radius:0!important;margin:0 -2px 0 0;border-right-width:2px}.ui.secondary.vertical.pointing.menu .active.item{border-color:#1B1C1D}.ui.secondary.inverted.pointing.menu{border-width:2px;border-color:rgba(34,36,38,.15)}.ui.secondary.inverted.pointing.menu .item{color:rgba(255,255,255,.9)}.ui.secondary.inverted.pointing.menu .header.item{color:#FFF!important}.ui.secondary.inverted.pointing.menu .link.item:hover,.ui.secondary.inverted.pointing.menu a.item:hover{color:rgba(0,0,0,.95)}.ui.secondary.inverted.pointing.menu .active.item{border-color:#FFF;color:#fff}.ui.text.menu{background:none;border-radius:0;box-shadow:none;border:none;margin:1em -.5em}.ui.text.menu .item{border-radius:0;box-shadow:none;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;margin:0;padding:.35714286em .5em;font-weight:400;color:rgba(0,0,0,.6);-webkit-transition:opacity .1s ease;transition:opacity .1s ease}.ui.text.menu .item:before,.ui.text.menu .menu .item:before{display:none!important}.ui.text.menu .header.item{background-color:transparent;opacity:1;color:rgba(0,0,0,.85);font-size:.92857143em;text-transform:uppercase;font-weight:700}.ui.text.item.menu .item,.ui.text.menu .item>img:not(.ui){margin:0}.ui.vertical.text.menu{margin:1em 0}.ui.vertical.text.menu:first-child{margin-top:0}.ui.vertical.text.menu:last-child{margin-bottom:0}.ui.vertical.text.menu .item{margin:.57142857em 0;padding-left:0;padding-right:0}.ui.vertical.text.menu .item>i.icon{float:none;margin:0 .35714286em 0 0}.ui.vertical.text.menu .header.item{margin:.57142857em 0 .71428571em}.ui.vertical.text.menu .item:not(.dropdown)>.menu{margin:0}.ui.vertical.text.menu .item:not(.dropdown)>.menu>.item{margin:0;padding:.5em 0}.ui.text.menu .item:hover{opacity:1;background-color:transparent}.ui.text.menu .active.item{background-color:transparent;border:none;box-shadow:none;font-weight:400;color:rgba(0,0,0,.95)}.ui.text.menu .active.item:hover{background-color:transparent}.ui.text.attached.menu,.ui.text.pointing.menu .active.item:after{box-shadow:none}.ui.inverted.text.menu,.ui.inverted.text.menu .active.item,.ui.inverted.text.menu .item,.ui.inverted.text.menu .item:hover{background-color:transparent!important}.ui.fluid.text.menu{margin-left:0;margin-right:0}.ui.vertical.icon.menu{display:inline-block;width:auto}.ui.icon.menu .item{height:auto;text-align:center;color:#1B1C1D}.ui.icon.menu .item>.icon:not(.dropdown){margin:0;opacity:1}.ui.icon.menu .icon:before{opacity:1}.ui.menu .icon.item>.icon{width:auto;margin:0 auto}.ui.vertical.icon.menu .item>.icon:not(.dropdown){display:block;opacity:1;margin:0 auto;float:none}.ui.inverted.icon.menu .item{color:#FFF}.ui.labeled.icon.menu{text-align:center}.ui.labeled.icon.menu .item{min-width:6em;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.ui.labeled.icon.menu .item>.icon:not(.dropdown){height:1em;display:block;font-size:1.71428571em!important;margin:0 auto .5rem!important}.ui.fluid.labeled.icon.menu>.item{min-width:0}@media only screen and (max-width:767px){.ui.stackable.menu{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.ui.stackable.menu .item{width:100%!important}.ui.stackable.menu .item:before{position:absolute;content:'';top:auto;bottom:0;left:0;width:100%;height:1px;background:rgba(34,36,38,.1)}.ui.stackable.menu .left.item,.ui.stackable.menu .left.menu{margin-right:0!important}.ui.stackable.menu .right.item,.ui.stackable.menu .right.menu{margin-left:0!important}}.ui.menu .red.active.item,.ui.red.menu .active.item{border-color:#DB2828!important;color:#DB2828!important}.ui.menu .orange.active.item,.ui.orange.menu .active.item{border-color:#F2711C!important;color:#F2711C!important}.ui.menu .yellow.active.item,.ui.yellow.menu .active.item{border-color:#FBBD08!important;color:#FBBD08!important}.ui.menu .olive.active.item,.ui.olive.menu .active.item{border-color:#B5CC18!important;color:#B5CC18!important}.ui.green.menu .active.item,.ui.menu .green.active.item{border-color:#21BA45!important;color:#21BA45!important}.ui.menu .teal.active.item,.ui.teal.menu .active.item{border-color:#00B5AD!important;color:#00B5AD!important}.ui.blue.menu .active.item,.ui.menu .blue.active.item{border-color:#2185D0!important;color:#2185D0!important}.ui.menu .violet.active.item,.ui.violet.menu .active.item{border-color:#6435C9!important;color:#6435C9!important}.ui.menu .purple.active.item,.ui.purple.menu .active.item{border-color:#A333C8!important;color:#A333C8!important}.ui.menu .pink.active.item,.ui.pink.menu .active.item{border-color:#E03997!important;color:#E03997!important}.ui.brown.menu .active.item,.ui.menu .brown.active.item{border-color:#A5673F!important;color:#A5673F!important}.ui.grey.menu .active.item,.ui.menu .grey.active.item{border-color:#767676!important;color:#767676!important}.ui.inverted.menu{border:0 solid transparent;background:#1B1C1D;box-shadow:none}.ui.inverted.menu .item,.ui.inverted.menu .item>a:not(.ui){background:0 0;color:rgba(255,255,255,.9)}.ui.inverted.menu .item.menu{background:0 0}.ui.inverted.menu .item:before,.ui.vertical.inverted.menu .item:before{background:rgba(255,255,255,.08)}.ui.vertical.inverted.menu .menu .item,.ui.vertical.inverted.menu .menu .item a:not(.ui){color:rgba(255,255,255,.5)}.ui.inverted.menu .header.item{margin:0;background:0 0;box-shadow:none}.ui.inverted.menu .item.disabled,.ui.inverted.menu .item.disabled:hover{color:rgba(225,225,225,.3)}.ui.inverted.menu .dropdown.item:hover,.ui.inverted.menu .link.item:hover,.ui.inverted.menu a.item:hover,.ui.link.inverted.menu .item:hover{background:rgba(255,255,255,.08);color:#fff}.ui.vertical.inverted.menu .item .menu .link.item:hover,.ui.vertical.inverted.menu .item .menu a.item:hover{background:0 0;color:#fff}.ui.inverted.menu .link.item:active,.ui.inverted.menu a.item:active{background:rgba(255,255,255,.08);color:#fff}.ui.inverted.menu .active.item{background:rgba(255,255,255,.15);color:#fff!important}.ui.inverted.vertical.menu .item .menu .active.item{background:0 0;color:#FFF}.ui.inverted.pointing.menu .active.item:after{background:#3D3E3F!important;margin:0!important;box-shadow:none!important;border:none!important}.ui.inverted.menu .active.item:hover{background:rgba(255,255,255,.15);color:#FFF!important}.ui.inverted.pointing.menu .active.item:hover:after{background:#3D3E3F!important}.ui.floated.menu{float:left;margin:0 .5rem 0 0}.ui.floated.menu .item:last-child:before{display:none}.ui.right.floated.menu{float:right;margin:0 0 0 .5rem}.ui.inverted.menu .red.active.item,.ui.inverted.red.menu{background-color:#DB2828}.ui.inverted.red.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.red.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.menu .orange.active.item,.ui.inverted.orange.menu{background-color:#F2711C}.ui.inverted.orange.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.orange.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.menu .yellow.active.item,.ui.inverted.yellow.menu{background-color:#FBBD08}.ui.inverted.yellow.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.yellow.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.menu .olive.active.item,.ui.inverted.olive.menu{background-color:#B5CC18}.ui.inverted.olive.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.olive.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.green.menu,.ui.inverted.menu .green.active.item{background-color:#21BA45}.ui.inverted.green.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.green.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.menu .teal.active.item,.ui.inverted.teal.menu{background-color:#00B5AD}.ui.inverted.teal.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.teal.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.blue.menu,.ui.inverted.menu .blue.active.item{background-color:#2185D0}.ui.inverted.blue.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.blue.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.menu .violet.active.item,.ui.inverted.violet.menu{background-color:#6435C9}.ui.inverted.violet.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.violet.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.menu .purple.active.item,.ui.inverted.purple.menu{background-color:#A333C8}.ui.inverted.purple.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.purple.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.menu .pink.active.item,.ui.inverted.pink.menu{background-color:#E03997}.ui.inverted.pink.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.pink.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.brown.menu,.ui.inverted.menu .brown.active.item{background-color:#A5673F}.ui.inverted.brown.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.brown.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.inverted.grey.menu,.ui.inverted.menu .grey.active.item{background-color:#767676}.ui.inverted.grey.menu .item:before{background-color:rgba(34,36,38,.1)}.ui.inverted.grey.menu .active.item{background-color:rgba(0,0,0,.1)!important}.ui.fitted.menu .item,.ui.fitted.menu .item .menu .item,.ui.menu .fitted.item{padding:0}.ui.horizontally.fitted.menu .item,.ui.horizontally.fitted.menu .item .menu .item,.ui.menu .horizontally.fitted.item{padding-top:.92857143em;padding-bottom:.92857143em}.ui.menu .vertically.fitted.item,.ui.vertically.fitted.menu .item,.ui.vertically.fitted.menu .item .menu .item{padding-left:1.14285714em;padding-right:1.14285714em}.ui.borderless.menu .item .menu .item:before,.ui.borderless.menu .item:before,.ui.menu .borderless.item:before{background:0 0!important}.ui.compact.menu{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;margin:0;vertical-align:middle}.ui.compact.vertical.menu{display:inline-block;width:auto!important}.ui.compact.menu .item:last-child{border-radius:0 .28571429rem .28571429rem 0}.ui.compact.menu .item:last-child:before{display:none}.ui.compact.vertical.menu .item:last-child::before{display:block}.ui.menu.fluid,.ui.vertical.menu.fluid{width:100%!important}.ui.item.menu,.ui.item.menu .item{width:100%;padding-left:0!important;padding-right:0!important;margin-left:0!important;margin-right:0!important;text-align:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.ui.item.menu .item:last-child:before{display:none}.ui.menu.two.item .item{width:50%}.ui.menu.three.item .item{width:33.333%}.ui.menu.four.item .item{width:25%}.ui.menu.five.item .item{width:20%}.ui.menu.six.item .item{width:16.666%}.ui.menu.seven.item .item{width:14.285%}.ui.menu.eight.item .item{width:12.5%}.ui.menu.nine.item .item{width:11.11%}.ui.menu.ten.item .item{width:10%}.ui.menu.eleven.item .item{width:9.09%}.ui.menu.twelve.item .item{width:8.333%}.ui.menu.fixed{position:fixed;z-index:101;margin:0;width:100%}.ui.menu.fixed,.ui.menu.fixed .item:first-child,.ui.menu.fixed .item:last-child{border-radius:0!important}.ui.fixed.menu,.ui[class*="top fixed"].menu{top:0;left:0;right:auto;bottom:auto}.ui[class*="top fixed"].menu{border-top:none;border-left:none;border-right:none}.ui[class*="right fixed"].menu{border-top:none;border-bottom:none;border-right:none;top:0;right:0;left:auto;bottom:auto;width:auto;height:100%}.ui[class*="bottom fixed"].menu{border-bottom:none;border-left:none;border-right:none;bottom:0;left:0;top:auto;right:auto}.ui[class*="left fixed"].menu{border-top:none;border-bottom:none;border-left:none;top:0;left:0;right:auto;bottom:auto;width:auto;height:100%}.ui.fixed.menu+.ui.grid{padding-top:2.75rem}.ui.pointing.menu .item:after{visibility:hidden;position:absolute;content:'';top:100%;left:50%;-webkit-transform:translateX(-50%) translateY(-50%) rotate(45deg);transform:translateX(-50%) translateY(-50%) rotate(45deg);background:0 0;margin:.5px 0 0;width:.57142857em;height:.57142857em;border:none;border-bottom:1px solid #D4D4D5;border-right:1px solid #D4D4D5;z-index:2;-webkit-transition:background .1s ease;transition:background .1s ease}.ui.vertical.pointing.menu .item:after{position:absolute;top:50%;right:0;bottom:auto;left:auto;-webkit-transform:translateX(50%) translateY(-50%) rotate(45deg);transform:translateX(50%) translateY(-50%) rotate(45deg);margin:0 -.5px 0 0;border:none;border-top:1px solid #D4D4D5;border-right:1px solid #D4D4D5}.ui.pointing.menu .active.item:after{visibility:visible}.ui.pointing.menu .active.dropdown.item:after{visibility:hidden}.ui.pointing.menu .active.item .menu .active.item:after,.ui.pointing.menu .dropdown.active.item:after{display:none}.ui.pointing.menu .active.item:after,.ui.pointing.menu .active.item:hover:after,.ui.vertical.pointing.menu .active.item:after,.ui.vertical.pointing.menu .active.item:hover:after{background-color:#F2F2F2}.ui.vertical.pointing.menu .menu .active.item:after{background-color:#FFF}.ui.attached.menu{top:0;bottom:0;border-radius:0;margin:0 -1px;width:calc(100% + 2px);max-width:calc(100% + 2px);box-shadow:none}.ui.attached+.ui.attached.menu:not(.top){border-top:none}.ui[class*="top attached"].menu{bottom:0;margin-bottom:0;top:0;margin-top:1rem;border-radius:.28571429rem .28571429rem 0 0}.ui.menu[class*="top attached"]:first-child{margin-top:0}.ui[class*="bottom attached"].menu{bottom:0;margin-top:0;top:0;margin-bottom:1rem;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),none;border-radius:0 0 .28571429rem .28571429rem}.ui[class*="bottom attached"].menu:last-child{margin-bottom:0}.ui.top.attached.menu>.item:first-child{border-radius:.28571429rem 0 0}.ui.bottom.attached.menu>.item:first-child{border-radius:0 0 0 .28571429rem}.ui.attached.menu:not(.tabular){border:1px solid #D4D4D5}.ui.attached.inverted.menu{border:none}.ui.attached.tabular.menu{margin-left:0;margin-right:0;width:100%}.ui.mini.menu{font-size:.78571429rem}.ui.mini.vertical.menu{width:9rem}.ui.tiny.menu{font-size:.85714286rem}.ui.tiny.vertical.menu{width:11rem}.ui.small.menu{font-size:.92857143rem}.ui.small.vertical.menu{width:13rem}.ui.menu{font-size:1rem}.ui.vertical.menu{width:15rem}.ui.large.menu{font-size:1.07142857rem}.ui.large.vertical.menu{width:18rem}.ui.huge.menu{font-size:1.14285714rem}.ui.huge.vertical.menu{width:20rem}.ui.big.menu{font-size:1.21428571rem}.ui.big.vertical.menu{width:22rem}.ui.massive.menu{font-size:1.28571429rem}.ui.massive.vertical.menu{width:25rem}.ui.message{position:relative;min-height:1em;margin:1em 0;background:#F8F8F9;padding:1em 1.5em;line-height:1.4285em;color:rgba(0,0,0,.87);-webkit-transition:opacity .1s ease,color .1s ease,background .1s ease,box-shadow .1s ease;transition:opacity .1s ease,color .1s ease,background .1s ease,box-shadow .1s ease;border-radius:.28571429rem;box-shadow:0 0 0 1px rgba(34,36,38,.22) inset,0 0 0 0 transparent}.ui.message:first-child{margin-top:0}.ui.message:last-child{margin-bottom:0}.ui.message .header{display:block;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:700;margin:-.14285em 0 0}.ui.message .header:not(.ui){font-size:1.14285714em}.ui.message p{opacity:.85;margin:.75em 0}.ui.message p:first-child{margin-top:0}.ui.message p:last-child{margin-bottom:0}.ui.message .header+p{margin-top:.25em}.ui.message .list:not(.ui){text-align:left;padding:0;opacity:.85;list-style-position:inside;margin:.5em 0 0}.ui.message .list:not(.ui):first-child{margin-top:0}.ui.message .list:not(.ui):last-child{margin-bottom:0}.ui.message .list:not(.ui) li{position:relative;list-style-type:none;margin:0 0 .3em 1em;padding:0}.ui.message .list:not(.ui) li:before{position:absolute;content:'•';left:-1em;height:100%;vertical-align:baseline}.ui.message .list:not(.ui) li:last-child{margin-bottom:0}.ui.message>.icon{margin-right:.6em}.ui.message>.close.icon{cursor:pointer;position:absolute;margin:0;top:.78575em;right:.5em;opacity:.7;-webkit-transition:opacity .1s ease;transition:opacity .1s ease}.ui.message>.close.icon:hover{opacity:1}.ui.message>:first-child{margin-top:0}.ui.message>:last-child{margin-bottom:0}.ui.dropdown .menu>.message{margin:0 -1px}.ui.visible.visible.visible.visible.message{display:block}.ui.icon.visible.visible.visible.visible.message{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.ui.hidden.hidden.hidden.hidden.message{display:none}.ui.compact.message{display:inline-block}.ui.attached.message{margin-bottom:-1px;border-radius:.28571429rem .28571429rem 0 0;box-shadow:0 0 0 1px rgba(34,36,38,.15) inset;margin-left:-1px;margin-right:-1px}.ui.attached+.ui.attached.message:not(.top):not(.bottom){margin-top:-1px;border-radius:0}.ui.bottom.attached.message{margin-top:-1px;border-radius:0 0 .28571429rem .28571429rem;box-shadow:0 0 0 1px rgba(34,36,38,.15) inset,0 1px 2px 0 rgba(34,36,38,.15)}.ui.bottom.attached.message:not(:last-child){margin-bottom:1em}.ui.attached.icon.message{width:auto}.ui.icon.message{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.ui.icon.message>.icon:not(.close){display:block;-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;line-height:1;vertical-align:middle;font-size:3em;opacity:.8}.ui.icon.message>.content{display:block;-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;vertical-align:middle}.ui.icon.message .icon:not(.close)+.content{padding-left:0}.ui.icon.message .circular.icon{width:1em}.ui.floating.message{box-shadow:0 0 0 1px rgba(34,36,38,.22) inset,0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.15)}.ui.positive.message{background-color:#FCFFF5;color:#2C662D}.ui.attached.positive.message,.ui.positive.message{box-shadow:0 0 0 1px #A3C293 inset,0 0 0 0 transparent}.ui.positive.message .header{color:#1A531B}.ui.negative.message{background-color:#FFF6F6;color:#9F3A38}.ui.attached.negative.message,.ui.negative.message{box-shadow:0 0 0 1px #E0B4B4 inset,0 0 0 0 transparent}.ui.negative.message .header{color:#912D2B}.ui.info.message{background-color:#F8FFFF;color:#276F86}.ui.attached.info.message,.ui.info.message{box-shadow:0 0 0 1px #A9D5DE inset,0 0 0 0 transparent}.ui.info.message .header{color:#0E566C}.ui.warning.message{background-color:#FFFAF3;color:#573A08}.ui.attached.warning.message,.ui.warning.message{box-shadow:0 0 0 1px #C9BA9B inset,0 0 0 0 transparent}.ui.warning.message .header{color:#794B02}.ui.error.message{background-color:#FFF6F6;color:#9F3A38}.ui.attached.error.message,.ui.error.message{box-shadow:0 0 0 1px #E0B4B4 inset,0 0 0 0 transparent}.ui.error.message .header{color:#912D2B}.ui.success.message{background-color:#FCFFF5;color:#2C662D}.ui.attached.success.message,.ui.success.message{box-shadow:0 0 0 1px #A3C293 inset,0 0 0 0 transparent}.ui.success.message .header{color:#1A531B}.ui.black.message,.ui.inverted.message{background-color:#1B1C1D;color:rgba(255,255,255,.9)}.ui.red.message{background-color:#FFE8E6;color:#DB2828;box-shadow:0 0 0 1px #DB2828 inset,0 0 0 0 transparent}.ui.red.message .header{color:#c82121}.ui.orange.message{background-color:#FFEDDE;color:#F2711C;box-shadow:0 0 0 1px #F2711C inset,0 0 0 0 transparent}.ui.orange.message .header{color:#e7640d}.ui.yellow.message{background-color:#FFF8DB;color:#B58105;box-shadow:0 0 0 1px #B58105 inset,0 0 0 0 transparent}.ui.yellow.message .header{color:#9c6f04}.ui.olive.message{background-color:#FBFDEF;color:#8ABC1E;box-shadow:0 0 0 1px #8ABC1E inset,0 0 0 0 transparent}.ui.olive.message .header{color:#7aa61a}.ui.green.message{background-color:#E5F9E7;color:#1EBC30;box-shadow:0 0 0 1px #1EBC30 inset,0 0 0 0 transparent}.ui.green.message .header{color:#1aa62a}.ui.teal.message{background-color:#E1F7F7;color:#10A3A3;box-shadow:0 0 0 1px #10A3A3 inset,0 0 0 0 transparent}.ui.teal.message .header{color:#0e8c8c}.ui.blue.message{background-color:#DFF0FF;color:#2185D0;box-shadow:0 0 0 1px #2185D0 inset,0 0 0 0 transparent}.ui.blue.message .header{color:#1e77ba}.ui.violet.message{background-color:#EAE7FF;color:#6435C9;box-shadow:0 0 0 1px #6435C9 inset,0 0 0 0 transparent}.ui.violet.message .header{color:#5a30b5}.ui.purple.message{background-color:#F6E7FF;color:#A333C8;box-shadow:0 0 0 1px #A333C8 inset,0 0 0 0 transparent}.ui.purple.message .header{color:#922eb4}.ui.pink.message{background-color:#FFE3FB;color:#E03997;box-shadow:0 0 0 1px #E03997 inset,0 0 0 0 transparent}.ui.pink.message .header{color:#dd238b}.ui.brown.message{background-color:#F1E2D3;color:#A5673F;box-shadow:0 0 0 1px #A5673F inset,0 0 0 0 transparent}.ui.brown.message .header{color:#935b38}.ui.mini.message{font-size:.78571429em}.ui.tiny.message{font-size:.85714286em}.ui.small.message{font-size:.92857143em}.ui.message{font-size:1em}.ui.large.message{font-size:1.14285714em}.ui.big.message{font-size:1.28571429em}.ui.huge.message{font-size:1.42857143em}.ui.massive.message{font-size:1.71428571em}.ui.table{width:100%;background:#FFF;margin:1em 0;border:1px solid rgba(34,36,38,.15);box-shadow:none;border-radius:.28571429rem;text-align:left;color:rgba(0,0,0,.87);border-collapse:separate;border-spacing:0}.ui.table:first-child{margin-top:0}.ui.table:last-child{margin-bottom:0}.ui.table td,.ui.table th{-webkit-transition:background .1s ease,color .1s ease;transition:background .1s ease,color .1s ease}.ui.table thead{box-shadow:none}.ui.table thead th{cursor:auto;background:#F9FAFB;text-align:inherit;color:rgba(0,0,0,.87);padding:.92857143em .78571429em;vertical-align:inherit;font-style:none;font-weight:700;text-transform:none;border-bottom:1px solid rgba(34,36,38,.1);border-left:none}.ui.table thead tr>th:first-child{border-left:none}.ui.table thead tr:first-child>th:first-child{border-radius:.28571429rem 0 0}.ui.table thead tr:first-child>th:last-child{border-radius:0 .28571429rem 0 0}.ui.table thead tr:first-child>th:only-child{border-radius:.28571429rem .28571429rem 0 0}.ui.table tfoot{box-shadow:none}.ui.table tfoot th{cursor:auto;border-top:1px solid rgba(34,36,38,.15);background:#F9FAFB;text-align:inherit;color:rgba(0,0,0,.87);padding:.78571429em;vertical-align:middle;font-style:normal;font-weight:400;text-transform:none}.ui.table tfoot tr>th:first-child{border-left:none}.ui.table tfoot tr:first-child>th:first-child{border-radius:0 0 0 .28571429rem}.ui.table tfoot tr:first-child>th:last-child{border-radius:0 0 .28571429rem}.ui.table tfoot tr:first-child>th:only-child{border-radius:0 0 .28571429rem .28571429rem}.ui.table tr td{border-top:1px solid rgba(34,36,38,.1)}.ui.table tr:first-child td{border-top:none}.ui.table td{padding:.78571429em;text-align:inherit}.ui.table>.icon{vertical-align:baseline}.ui.table>.icon:only-child{margin:0}.ui.table.segment{padding:0}.ui.table.segment:after{display:none}.ui.table.segment.stacked:after{display:block}@media only screen and (max-width:767px){.ui.table:not(.unstackable){width:100%;padding:0}.ui.table:not(.unstackable) tbody,.ui.table:not(.unstackable) tr,.ui.table:not(.unstackable) tr>td,.ui.table:not(.unstackable) tr>th{width:auto!important;display:block!important}.ui.table:not(.unstackable) tfoot,.ui.table:not(.unstackable) thead{display:block}.ui.table:not(.unstackable) tr{padding-top:1em;padding-bottom:1em;box-shadow:0 -1px 0 0 rgba(0,0,0,.1) inset!important}.ui.table:not(.unstackable) tr>td,.ui.table:not(.unstackable) tr>th{background:0 0;border:none!important;padding:.25em .75em!important;box-shadow:none!important}.ui.table:not(.unstackable) td:first-child,.ui.table:not(.unstackable) th:first-child{font-weight:700}.ui.definition.table:not(.unstackable) thead th:first-child{box-shadow:none!important}}.ui.table td .image,.ui.table td .image img,.ui.table th .image,.ui.table th .image img{max-width:none}.ui.structured.table{border-collapse:collapse}.ui.structured.table thead th{border-left:none;border-right:none}.ui.structured.sortable.table thead th{border-left:1px solid rgba(34,36,38,.15);border-right:1px solid rgba(34,36,38,.15)}.ui.structured.basic.table th{border-left:none;border-right:none}.ui.structured.celled.table tr td,.ui.structured.celled.table tr th{border-left:1px solid rgba(34,36,38,.1);border-right:1px solid rgba(34,36,38,.1)}.ui.definition.table thead:not(.full-width) th:first-child{pointer-events:none;background:0 0;font-weight:400;color:rgba(0,0,0,.4);box-shadow:-1px -1px 0 1px #FFF}.ui.definition.table tfoot:not(.full-width) th:first-child{pointer-events:none;background:0 0;font-weight:rgba(0,0,0,.4);color:normal;box-shadow:1px 1px 0 1px #FFF}.ui.celled.definition.table thead:not(.full-width) th:first-child{box-shadow:0 -1px 0 1px #FFF}.ui.celled.definition.table tfoot:not(.full-width) th:first-child{box-shadow:0 1px 0 1px #FFF}.ui.definition.table tr td.definition,.ui.definition.table tr td:first-child:not(.ignored){background:rgba(0,0,0,.03);font-weight:700;color:rgba(0,0,0,.95);text-transform:'';box-shadow:'';text-align:'';font-size:1em;padding-left:'';padding-right:''}.ui.definition.table td:nth-child(2),.ui.definition.table tfoot:not(.full-width) th:nth-child(2),.ui.definition.table thead:not(.full-width) th:nth-child(2){border-left:1px solid rgba(34,36,38,.15)}.ui.table td.positive,.ui.table tr.positive{box-shadow:0 0 0 #A3C293 inset;background:#FCFFF5!important;color:#2C662D!important}.ui.table td.negative,.ui.table tr.negative{box-shadow:0 0 0 #E0B4B4 inset;background:#FFF6F6!important;color:#9F3A38!important}.ui.table td.error,.ui.table tr.error{box-shadow:0 0 0 #E0B4B4 inset;background:#FFF6F6!important;color:#9F3A38!important}.ui.table td.warning,.ui.table tr.warning{box-shadow:0 0 0 #C9BA9B inset;background:#FFFAF3!important;color:#573A08!important}.ui.table td.active,.ui.table tr.active{box-shadow:0 0 0 rgba(0,0,0,.87) inset;background:#E0E0E0!important;color:rgba(0,0,0,.87)!important}.ui.table tr td.disabled,.ui.table tr.disabled td,.ui.table tr.disabled:hover,.ui.table tr:hover td.disabled{pointer-events:none;color:rgba(40,40,40,.3)}@media only screen and (max-width:991px){.ui[class*="tablet stackable"].table,.ui[class*="tablet stackable"].table tbody,.ui[class*="tablet stackable"].table tr,.ui[class*="tablet stackable"].table tr>td,.ui[class*="tablet stackable"].table tr>th{width:100%!important;display:block!important}.ui[class*="tablet stackable"].table{padding:0}.ui[class*="tablet stackable"].table tfoot,.ui[class*="tablet stackable"].table thead{display:block}.ui[class*="tablet stackable"].table tr{padding-top:1em;padding-bottom:1em;box-shadow:0 -1px 0 0 rgba(0,0,0,.1) inset!important}.ui[class*="tablet stackable"].table tr>td,.ui[class*="tablet stackable"].table tr>th{background:0 0;border:none!important;padding:.25em .75em;box-shadow:none!important}.ui.definition[class*="tablet stackable"].table thead th:first-child{box-shadow:none!important}}.ui.table [class*="left aligned"],.ui.table[class*="left aligned"]{text-align:left}.ui.table [class*="center aligned"],.ui.table[class*="center aligned"]{text-align:center}.ui.table [class*="right aligned"],.ui.table[class*="right aligned"]{text-align:right}.ui.table [class*="top aligned"],.ui.table[class*="top aligned"]{vertical-align:top}.ui.table [class*="middle aligned"],.ui.table[class*="middle aligned"]{vertical-align:middle}.ui.table [class*="bottom aligned"],.ui.table[class*="bottom aligned"]{vertical-align:bottom}.ui.table td.collapsing,.ui.table th.collapsing{width:1px;white-space:nowrap}.ui.fixed.table{table-layout:fixed}.ui.fixed.table td,.ui.fixed.table th{overflow:hidden;text-overflow:ellipsis}.ui.selectable.table tbody tr:hover,.ui.table tbody tr td.selectable:hover{background:rgba(0,0,0,.05)!important;color:rgba(0,0,0,.95)!important}.ui.inverted.table tbody tr td.selectable:hover,.ui.selectable.inverted.table tbody tr:hover{background:rgba(255,255,255,.08)!important;color:#fff!important}.ui.table tbody tr td.selectable{padding:0}.ui.table tbody tr td.selectable>a:not(.ui){display:block;color:inherit;padding:.78571429em}.ui.selectable.table tr.error:hover,.ui.selectable.table tr:hover td.error,.ui.table tr td.selectable.error:hover{background:#ffe7e7!important;color:#943634!important}.ui.selectable.table tr.warning:hover,.ui.selectable.table tr:hover td.warning,.ui.table tr td.selectable.warning:hover{background:#fff4e4!important;color:#493107!important}.ui.selectable.table tr.active:hover,.ui.selectable.table tr:hover td.active,.ui.table tr td.selectable.active:hover{background:#E0E0E0!important;color:rgba(0,0,0,.87)!important}.ui.selectable.table tr.positive:hover,.ui.selectable.table tr:hover td.positive,.ui.table tr td.selectable.positive:hover{background:#f7ffe6!important;color:#275b28!important}.ui.selectable.table tr.negative:hover,.ui.selectable.table tr:hover td.negative,.ui.table tr td.selectable.negative:hover{background:#ffe7e7!important;color:#943634!important}.ui.attached.table{top:0;bottom:0;border-radius:0;margin:0 -1px;width:calc(100% + 2px);max-width:calc(100% + 2px);box-shadow:none;border:1px solid #D4D4D5}.ui.attached+.ui.attached.table:not(.top){border-top:none}.ui[class*="top attached"].table{bottom:0;margin-bottom:0;top:0;margin-top:1em;border-radius:.28571429rem .28571429rem 0 0}.ui.table[class*="top attached"]:first-child{margin-top:0}.ui[class*="bottom attached"].table{bottom:0;margin-top:0;top:0;margin-bottom:1em;box-shadow:none,none;border-radius:0 0 .28571429rem .28571429rem}.ui[class*="bottom attached"].table:last-child{margin-bottom:0}.ui.striped.table tbody tr:nth-child(2n),.ui.striped.table>tr:nth-child(2n){background-color:rgba(0,0,50,.02)}.ui.inverted.striped.table tbody tr:nth-child(2n),.ui.inverted.striped.table>tr:nth-child(2n){background-color:rgba(255,255,255,.05)}.ui.striped.selectable.selectable.selectable.table tbody tr.active:hover{background:#EFEFEF!important;color:rgba(0,0,0,.95)!important}.ui.table [class*="single line"],.ui.table[class*="single line"]{white-space:nowrap}.ui.red.table{border-top:.2em solid #DB2828}.ui.inverted.red.table{background-color:#DB2828!important;color:#FFF!important}.ui.orange.table{border-top:.2em solid #F2711C}.ui.inverted.orange.table{background-color:#F2711C!important;color:#FFF!important}.ui.yellow.table{border-top:.2em solid #FBBD08}.ui.inverted.yellow.table{background-color:#FBBD08!important;color:#FFF!important}.ui.olive.table{border-top:.2em solid #B5CC18}.ui.inverted.olive.table{background-color:#B5CC18!important;color:#FFF!important}.ui.green.table{border-top:.2em solid #21BA45}.ui.inverted.green.table{background-color:#21BA45!important;color:#FFF!important}.ui.teal.table{border-top:.2em solid #00B5AD}.ui.inverted.teal.table{background-color:#00B5AD!important;color:#FFF!important}.ui.blue.table{border-top:.2em solid #2185D0}.ui.inverted.blue.table{background-color:#2185D0!important;color:#FFF!important}.ui.violet.table{border-top:.2em solid #6435C9}.ui.inverted.violet.table{background-color:#6435C9!important;color:#FFF!important}.ui.purple.table{border-top:.2em solid #A333C8}.ui.inverted.purple.table{background-color:#A333C8!important;color:#FFF!important}.ui.pink.table{border-top:.2em solid #E03997}.ui.inverted.pink.table{background-color:#E03997!important;color:#FFF!important}.ui.brown.table{border-top:.2em solid #A5673F}.ui.inverted.brown.table{background-color:#A5673F!important;color:#FFF!important}.ui.grey.table{border-top:.2em solid #767676}.ui.inverted.grey.table{background-color:#767676!important;color:#FFF!important}.ui.black.table{border-top:.2em solid #1B1C1D}.ui.inverted.black.table{background-color:#1B1C1D!important;color:#FFF!important}.ui.one.column.table td{width:100%}.ui.two.column.table td{width:50%}.ui.three.column.table td{width:33.33333333%}.ui.four.column.table td{width:25%}.ui.five.column.table td{width:20%}.ui.six.column.table td{width:16.66666667%}.ui.seven.column.table td{width:14.28571429%}.ui.eight.column.table td{width:12.5%}.ui.nine.column.table td{width:11.11111111%}.ui.ten.column.table td{width:10%}.ui.eleven.column.table td{width:9.09090909%}.ui.twelve.column.table td{width:8.33333333%}.ui.thirteen.column.table td{width:7.69230769%}.ui.fourteen.column.table td{width:7.14285714%}.ui.fifteen.column.table td{width:6.66666667%}.ui.sixteen.column.table td,.ui.table td.one.wide,.ui.table th.one.wide{width:6.25%}.ui.table td.two.wide,.ui.table th.two.wide{width:12.5%}.ui.table td.three.wide,.ui.table th.three.wide{width:18.75%}.ui.table td.four.wide,.ui.table th.four.wide{width:25%}.ui.table td.five.wide,.ui.table th.five.wide{width:31.25%}.ui.table td.six.wide,.ui.table th.six.wide{width:37.5%}.ui.table td.seven.wide,.ui.table th.seven.wide{width:43.75%}.ui.table td.eight.wide,.ui.table th.eight.wide{width:50%}.ui.table td.nine.wide,.ui.table th.nine.wide{width:56.25%}.ui.table td.ten.wide,.ui.table th.ten.wide{width:62.5%}.ui.table td.eleven.wide,.ui.table th.eleven.wide{width:68.75%}.ui.table td.twelve.wide,.ui.table th.twelve.wide{width:75%}.ui.table td.thirteen.wide,.ui.table th.thirteen.wide{width:81.25%}.ui.table td.fourteen.wide,.ui.table th.fourteen.wide{width:87.5%}.ui.table td.fifteen.wide,.ui.table th.fifteen.wide{width:93.75%}.ui.table td.sixteen.wide,.ui.table th.sixteen.wide{width:100%}.ui.sortable.table thead th{cursor:pointer;white-space:nowrap;border-left:1px solid rgba(34,36,38,.15);color:rgba(0,0,0,.87)}.ui.sortable.table thead th:first-child{border-left:none}.ui.sortable.table thead th.sorted,.ui.sortable.table thead th.sorted:hover{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ui.sortable.table thead th:after{display:none;font-style:normal;font-weight:400;text-decoration:inherit;content:'';height:1em;width:auto;opacity:.8;margin:0 0 0 .5em;font-family:Icons}.ui.sortable.table thead th.ascending:after{content:'\f0d8'}.ui.sortable.table thead th.descending:after{content:'\f0d7'}.ui.sortable.table th.disabled:hover{cursor:auto;color:rgba(40,40,40,.3)}.ui.sortable.table thead th:hover{background:rgba(0,0,0,.05);color:rgba(0,0,0,.8)}.ui.sortable.table thead th.sorted{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95)}.ui.sortable.table thead th.sorted:after{display:inline-block}.ui.sortable.table thead th.sorted:hover{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95)}.ui.inverted.sortable.table thead th.sorted{background:-webkit-linear-gradient(transparent,rgba(0,0,0,.05)) rgba(255,255,255,.15);background:linear-gradient(transparent,rgba(0,0,0,.05)) rgba(255,255,255,.15);color:#fff}.ui.inverted.sortable.table thead th:hover{background:-webkit-linear-gradient(transparent,rgba(0,0,0,.05)) rgba(255,255,255,.08);background:linear-gradient(transparent,rgba(0,0,0,.05)) rgba(255,255,255,.08);color:#fff}.ui.inverted.sortable.table thead th{border-left-color:transparent;border-right-color:transparent}.ui.inverted.table{background:#333;color:rgba(255,255,255,.9);border:none}.ui.inverted.table th{background-color:rgba(0,0,0,.15);border-color:rgba(255,255,255,.1)!important;color:rgba(255,255,255,.9)}.ui.inverted.table tr td{border-color:rgba(255,255,255,.1)!important}.ui.inverted.table tr td.disabled,.ui.inverted.table tr.disabled td,.ui.inverted.table tr.disabled:hover td,.ui.inverted.table tr:hover td.disabled{pointer-events:none;color:rgba(225,225,225,.3)}.ui.inverted.definition.table tfoot:not(.full-width) th:first-child,.ui.inverted.definition.table thead:not(.full-width) th:first-child{background:#FFF}.ui.inverted.definition.table tr td:first-child{background:rgba(255,255,255,.02);color:#fff}.ui.collapsing.table{width:auto}.ui.basic.table{background:0 0;border:1px solid rgba(34,36,38,.15);box-shadow:none}.ui.basic.table tfoot,.ui.basic.table thead{box-shadow:none}.ui.basic.table th{background:0 0;border-left:none}.ui.basic.table tbody tr{border-bottom:1px solid rgba(0,0,0,.1)}.ui.basic.table td{background:0 0}.ui.basic.striped.table tbody tr:nth-child(2n){background-color:rgba(0,0,0,.05)!important}.ui[class*="very basic"].table{border:none}.ui[class*="very basic"].table:not(.sortable):not(.striped) td,.ui[class*="very basic"].table:not(.sortable):not(.striped) th{padding:''}.ui[class*="very basic"].table:not(.sortable):not(.striped) td:first-child,.ui[class*="very basic"].table:not(.sortable):not(.striped) th:first-child{padding-left:0}.ui[class*="very basic"].table:not(.sortable):not(.striped) td:last-child,.ui[class*="very basic"].table:not(.sortable):not(.striped) th:last-child{padding-right:0}.ui[class*="very basic"].table:not(.sortable):not(.striped) thead tr:first-child th{padding-top:0}.ui.celled.table tr td,.ui.celled.table tr th{border-left:1px solid rgba(34,36,38,.1)}.ui.celled.table tr td:first-child,.ui.celled.table tr th:first-child{border-left:none}.ui.padded.table th{padding-left:1em;padding-right:1em}.ui.padded.table td,.ui.padded.table th{padding:1em}.ui[class*="very padded"].table th{padding-left:1.5em;padding-right:1.5em}.ui[class*="very padded"].table td{padding:1.5em}.ui.compact.table th{padding-left:.7em;padding-right:.7em}.ui.compact.table td{padding:.5em .7em}.ui[class*="very compact"].table th{padding-left:.6em;padding-right:.6em}.ui[class*="very compact"].table td{padding:.4em .6em}.ui.small.table{font-size:.9em}.ui.table{font-size:1em}.ui.large.table{font-size:1.1em}.ui.ad{display:block;overflow:hidden;margin:1em 0}.ui.ad:first-child,.ui.ad:last-child{margin:0}.ui.ad iframe{margin:0;padding:0;border:none;overflow:hidden}.ui.leaderboard.ad{width:728px;height:90px}.ui[class*="medium rectangle"].ad{width:300px;height:250px}.ui[class*="large rectangle"].ad{width:336px;height:280px}.ui[class*="half page"].ad{width:300px;height:600px}.ui.square.ad{width:250px;height:250px}.ui[class*="small square"].ad{width:200px;height:200px}.ui[class*="small rectangle"].ad{width:180px;height:150px}.ui[class*="vertical rectangle"].ad{width:240px;height:400px}.ui.button.ad{width:120px;height:90px}.ui[class*="square button"].ad{width:125px;height:125px}.ui[class*="small button"].ad{width:120px;height:60px}.ui.skyscraper.ad{width:120px;height:600px}.ui[class*="wide skyscraper"].ad{width:160px}.ui.banner.ad{width:468px;height:60px}.ui[class*="vertical banner"].ad{width:120px;height:240px}.ui[class*="top banner"].ad{width:930px;height:180px}.ui[class*="half banner"].ad{width:234px;height:60px}.ui[class*="large leaderboard"].ad{width:970px;height:90px}.ui.billboard.ad{width:970px;height:250px}.ui.panorama.ad{width:980px;height:120px}.ui.netboard.ad{width:580px;height:400px}.ui[class*="large mobile banner"].ad{width:320px;height:100px}.ui[class*="mobile leaderboard"].ad{width:320px;height:50px}.ui.mobile.ad{display:none}@media only screen and (max-width:767px){.ui.mobile.ad{display:block}}.ui.centered.ad{margin-left:auto;margin-right:auto}.ui.test.ad{position:relative;background:#545454}.ui.test.ad:after{position:absolute;top:50%;left:50%;width:100%;text-align:center;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%);content:'Ad';color:#FFF;font-size:1em;font-weight:700}.ui.mobile.test.ad:after{font-size:.85714286em}.ui.test.ad[data-text]:after{content:attr(data-text)}.ui.card,.ui.cards>.card{max-width:100%;position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;width:290px;min-height:0;background:#FFF;padding:0;border:none;border-radius:.28571429rem;box-shadow:0 1px 3px 0 #D4D4D5,0 0 0 1px #D4D4D5;-webkit-transition:box-shadow .1s ease,-webkit-transform .1s ease;transition:box-shadow .1s ease,-webkit-transform .1s ease;transition:box-shadow .1s ease,transform .1s ease;transition:box-shadow .1s ease,transform .1s ease,-webkit-transform .1s ease;z-index:''}.ui.card{margin:1em 0}.ui.card a,.ui.cards>.card a{cursor:pointer}.ui.card:first-child{margin-top:0}.ui.card:last-child{margin-bottom:0}.ui.cards{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin:-.875em -.5em;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.ui.cards>.card{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin:.875em .5em;float:none}.ui.card:after,.ui.cards:after{display:block;content:' ';height:0;clear:both;overflow:hidden;visibility:hidden}.ui.cards~.ui.cards{margin-top:.875em}.ui.card>:first-child,.ui.cards>.card>:first-child{border-radius:.28571429rem .28571429rem 0 0!important;border-top:none!important}.ui.card>:last-child,.ui.cards>.card>:last-child{border-radius:0 0 .28571429rem .28571429rem!important}.ui.card>:only-child,.ui.cards>.card>:only-child{border-radius:.28571429rem!important}.ui.card>.image,.ui.cards>.card>.image{position:relative;display:block;-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;padding:0;background:rgba(0,0,0,.05)}.ui.card>.image>img,.ui.cards>.card>.image>img{display:block;width:100%;height:auto;border-radius:inherit}.ui.card>.image:not(.ui)>img,.ui.cards>.card>.image:not(.ui)>img{border:none}.ui.card>.content,.ui.cards>.card>.content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;border:none;border-top:1px solid rgba(34,36,38,.1);background:0 0;margin:0;padding:1em;box-shadow:none;font-size:1em;border-radius:0}.ui.card>.content:after,.ui.cards>.card>.content:after{display:block;content:' ';height:0;clear:both;overflow:hidden;visibility:hidden}.ui.card>.content>.header,.ui.cards>.card>.content>.header{display:block;margin:'';font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;color:rgba(0,0,0,.85)}.ui.card>.content>.header:not(.ui),.ui.cards>.card>.content>.header:not(.ui){font-weight:700;font-size:1.28571429em;margin-top:-.21425em;line-height:1.2857em}.ui.card>.content>.header+.description,.ui.card>.content>.meta+.description,.ui.cards>.card>.content>.header+.description,.ui.cards>.card>.content>.meta+.description{margin-top:.5em}.ui.card [class*="left floated"],.ui.cards>.card [class*="left floated"]{float:left}.ui.card [class*="right floated"],.ui.cards>.card [class*="right floated"]{float:right}.ui.card [class*="left aligned"],.ui.cards>.card [class*="left aligned"]{text-align:left}.ui.card [class*="center aligned"],.ui.cards>.card [class*="center aligned"]{text-align:center}.ui.card [class*="right aligned"],.ui.cards>.card [class*="right aligned"]{text-align:right}.ui.card .content img,.ui.cards>.card .content img{display:inline-block;vertical-align:middle;width:''}.ui.card .avatar img,.ui.card img.avatar,.ui.cards>.card .avatar img,.ui.cards>.card img.avatar{width:2em;height:2em;border-radius:500rem}.ui.card>.content>.description,.ui.cards>.card>.content>.description{clear:both;color:rgba(0,0,0,.68)}.ui.card>.content p,.ui.cards>.card>.content p{margin:0 0 .5em}.ui.card>.content p:last-child,.ui.cards>.card>.content p:last-child{margin-bottom:0}.ui.card .meta,.ui.cards>.card .meta{font-size:1em;color:rgba(0,0,0,.4)}.ui.card .meta *,.ui.cards>.card .meta *{margin-right:.3em}.ui.card .meta :last-child,.ui.cards>.card .meta :last-child{margin-right:0}.ui.card .meta [class*="right floated"],.ui.cards>.card .meta [class*="right floated"]{margin-right:0;margin-left:.3em}.ui.card>.content a:not(.ui),.ui.cards>.card>.content a:not(.ui){color:'';-webkit-transition:color .1s ease;transition:color .1s ease}.ui.card>.content a:not(.ui):hover,.ui.cards>.card>.content a:not(.ui):hover{color:''}.ui.card>.content>a.header,.ui.cards>.card>.content>a.header{color:rgba(0,0,0,.85)}.ui.card>.content>a.header:hover,.ui.cards>.card>.content>a.header:hover{color:#1e70bf}.ui.card .meta>a:not(.ui),.ui.cards>.card .meta>a:not(.ui){color:rgba(0,0,0,.4)}.ui.card .meta>a:not(.ui):hover,.ui.cards>.card .meta>a:not(.ui):hover{color:rgba(0,0,0,.87)}.ui.card>.button,.ui.card>.buttons,.ui.cards>.card>.button,.ui.cards>.card>.buttons{margin:0 -1px;width:calc(100% + 2px)}.ui.card .dimmer,.ui.cards>.card .dimmer{background-color:'';z-index:10}.ui.card>.content .star.icon,.ui.cards>.card>.content .star.icon{cursor:pointer;opacity:.75;-webkit-transition:color .1s ease;transition:color .1s ease}.ui.card>.content .star.icon:hover,.ui.cards>.card>.content .star.icon:hover{opacity:1;color:#FFB70A}.ui.card>.content .active.star.icon,.ui.cards>.card>.content .active.star.icon{color:#FFE623}.ui.card>.content .like.icon,.ui.cards>.card>.content .like.icon{cursor:pointer;opacity:.75;-webkit-transition:color .1s ease;transition:color .1s ease}.ui.card>.content .like.icon:hover,.ui.cards>.card>.content .like.icon:hover{opacity:1;color:#FF2733}.ui.card>.content .active.like.icon,.ui.cards>.card>.content .active.like.icon{color:#FF2733}.ui.card>.extra,.ui.cards>.card>.extra{max-width:100%;min-height:0!important;-webkit-box-flex:0;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;border-top:1px solid rgba(0,0,0,.05)!important;position:static;background:0 0;width:auto;margin:0;padding:.75em 1em;top:0;left:0;color:rgba(0,0,0,.4);box-shadow:none;-webkit-transition:color .1s ease;transition:color .1s ease}.ui.card>.extra a:not(.ui),.ui.cards>.card>.extra a:not(.ui){color:rgba(0,0,0,.4)}.ui.card>.extra a:not(.ui):hover,.ui.cards>.card>.extra a:not(.ui):hover{color:#1e70bf}.ui.link.cards .raised.card:hover,.ui.link.raised.card:hover,.ui.raised.cards a.card:hover,a.ui.raised.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 4px 0 rgba(34,36,38,.15),0 2px 10px 0 rgba(34,36,38,.25)}.ui.raised.card,.ui.raised.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.15)}.ui.centered.cards{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.ui.centered.card{margin-left:auto;margin-right:auto}.ui.fluid.card{width:100%;max-width:9999px}.ui.cards a.card,.ui.link.card,.ui.link.cards .card,a.ui.card{-webkit-transform:none;transform:none}.ui.cards a.card:hover,.ui.link.card:hover,.ui.link.cards .card:hover,a.ui.card:hover{cursor:pointer;z-index:5;background:#FFF;border:none;box-shadow:0 1px 3px 0 #BCBDBD,0 0 0 1px #D4D4D5;-webkit-transform:translateY(-3px);transform:translateY(-3px)}.ui.cards>.red.card,.ui.red.card,.ui.red.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #DB2828,0 1px 3px 0 #D4D4D5}.ui.cards>.red.card:hover,.ui.red.card:hover,.ui.red.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #d01919,0 1px 3px 0 #BCBDBD}.ui.cards>.orange.card,.ui.orange.card,.ui.orange.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #F2711C,0 1px 3px 0 #D4D4D5}.ui.cards>.orange.card:hover,.ui.orange.card:hover,.ui.orange.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #f26202,0 1px 3px 0 #BCBDBD}.ui.cards>.yellow.card,.ui.yellow.card,.ui.yellow.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #FBBD08,0 1px 3px 0 #D4D4D5}.ui.cards>.yellow.card:hover,.ui.yellow.card:hover,.ui.yellow.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #eaae00,0 1px 3px 0 #BCBDBD}.ui.cards>.olive.card,.ui.olive.card,.ui.olive.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #B5CC18,0 1px 3px 0 #D4D4D5}.ui.cards>.olive.card:hover,.ui.olive.card:hover,.ui.olive.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #a7bd0d,0 1px 3px 0 #BCBDBD}.ui.cards>.green.card,.ui.green.card,.ui.green.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #21BA45,0 1px 3px 0 #D4D4D5}.ui.cards>.green.card:hover,.ui.green.card:hover,.ui.green.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #16ab39,0 1px 3px 0 #BCBDBD}.ui.cards>.teal.card,.ui.teal.card,.ui.teal.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #00B5AD,0 1px 3px 0 #D4D4D5}.ui.cards>.teal.card:hover,.ui.teal.card:hover,.ui.teal.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #009c95,0 1px 3px 0 #BCBDBD}.ui.blue.card,.ui.blue.cards>.card,.ui.cards>.blue.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #2185D0,0 1px 3px 0 #D4D4D5}.ui.blue.card:hover,.ui.blue.cards>.card:hover,.ui.cards>.blue.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #1678c2,0 1px 3px 0 #BCBDBD}.ui.cards>.violet.card,.ui.violet.card,.ui.violet.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #6435C9,0 1px 3px 0 #D4D4D5}.ui.cards>.violet.card:hover,.ui.violet.card:hover,.ui.violet.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #5829bb,0 1px 3px 0 #BCBDBD}.ui.cards>.purple.card,.ui.purple.card,.ui.purple.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #A333C8,0 1px 3px 0 #D4D4D5}.ui.cards>.purple.card:hover,.ui.purple.card:hover,.ui.purple.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #9627ba,0 1px 3px 0 #BCBDBD}.ui.cards>.pink.card,.ui.pink.card,.ui.pink.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #E03997,0 1px 3px 0 #D4D4D5}.ui.cards>.pink.card:hover,.ui.pink.card:hover,.ui.pink.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #e61a8d,0 1px 3px 0 #BCBDBD}.ui.brown.card,.ui.brown.cards>.card,.ui.cards>.brown.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #A5673F,0 1px 3px 0 #D4D4D5}.ui.brown.card:hover,.ui.brown.cards>.card:hover,.ui.cards>.brown.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #975b33,0 1px 3px 0 #BCBDBD}.ui.cards>.grey.card,.ui.grey.card,.ui.grey.cards>.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #767676,0 1px 3px 0 #D4D4D5}.ui.cards>.grey.card:hover,.ui.grey.card:hover,.ui.grey.cards>.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #838383,0 1px 3px 0 #BCBDBD}.ui.black.card,.ui.black.cards>.card,.ui.cards>.black.card{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #1B1C1D,0 1px 3px 0 #D4D4D5}.ui.black.card:hover,.ui.black.cards>.card:hover,.ui.cards>.black.card:hover{box-shadow:0 0 0 1px #D4D4D5,0 2px 0 0 #27292a,0 1px 3px 0 #BCBDBD}.ui.one.cards{margin-left:0;margin-right:0}.ui.one.cards>.card{width:100%}.ui.two.cards{margin-left:-1em;margin-right:-1em}.ui.two.cards>.card{width:calc(50% - 2em);margin-left:1em;margin-right:1em}.ui.three.cards{margin-left:-1em;margin-right:-1em}.ui.three.cards>.card{width:calc(33.33333333% - 2em);margin-left:1em;margin-right:1em}.ui.four.cards{margin-left:-.75em;margin-right:-.75em}.ui.four.cards>.card{width:calc(25% - 1.5em);margin-left:.75em;margin-right:.75em}.ui.five.cards{margin-left:-.75em;margin-right:-.75em}.ui.five.cards>.card{width:calc(20% - 1.5em);margin-left:.75em;margin-right:.75em}.ui.six.cards{margin-left:-.75em;margin-right:-.75em}.ui.six.cards>.card{width:calc(16.66666667% - 1.5em);margin-left:.75em;margin-right:.75em}.ui.seven.cards{margin-left:-.5em;margin-right:-.5em}.ui.seven.cards>.card{width:calc(14.28571429% - 1em);margin-left:.5em;margin-right:.5em}.ui.eight.cards{margin-left:-.5em;margin-right:-.5em}.ui.eight.cards>.card{width:calc(12.5% - 1em);margin-left:.5em;margin-right:.5em;font-size:11px}.ui.nine.cards{margin-left:-.5em;margin-right:-.5em}.ui.nine.cards>.card{width:calc(11.11111111% - 1em);margin-left:.5em;margin-right:.5em;font-size:10px}.ui.ten.cards{margin-left:-.5em;margin-right:-.5em}.ui.ten.cards>.card{width:calc(10% - 1em);margin-left:.5em;margin-right:.5em}@media only screen and (max-width:767px){.ui.two.doubling.cards{margin-left:0;margin-right:0}.ui.two.doubling.cards .card{width:100%;margin-left:0;margin-right:0}.ui.three.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.three.doubling.cards .card{width:calc(50% - 2em);margin-left:1em;margin-right:1em}.ui.four.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.four.doubling.cards .card{width:calc(50% - 2em);margin-left:1em;margin-right:1em}.ui.five.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.five.doubling.cards .card{width:calc(50% - 2em);margin-left:1em;margin-right:1em}.ui.six.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.six.doubling.cards .card{width:calc(50% - 2em);margin-left:1em;margin-right:1em}.ui.seven.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.seven.doubling.cards .card{width:calc(33.33333333% - 2em);margin-left:1em;margin-right:1em}.ui.eight.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.eight.doubling.cards .card{width:calc(33.33333333% - 2em);margin-left:1em;margin-right:1em}.ui.nine.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.nine.doubling.cards .card{width:calc(33.33333333% - 2em);margin-left:1em;margin-right:1em}.ui.ten.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.ten.doubling.cards .card{width:calc(33.33333333% - 2em);margin-left:1em;margin-right:1em}}@media only screen and (min-width:768px) and (max-width:991px){.ui.two.doubling.cards{margin-left:0;margin-right:0}.ui.two.doubling.cards .card{width:100%;margin-left:0;margin-right:0}.ui.three.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.three.doubling.cards .card{width:calc(50% - 2em);margin-left:1em;margin-right:1em}.ui.four.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.four.doubling.cards .card{width:calc(50% - 2em);margin-left:1em;margin-right:1em}.ui.five.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.five.doubling.cards .card{width:calc(33.33333333% - 2em);margin-left:1em;margin-right:1em}.ui.six.doubling.cards{margin-left:-1em;margin-right:-1em}.ui.six.doubling.cards .card{width:calc(33.33333333% - 2em);margin-left:1em;margin-right:1em}.ui.eight.doubling.cards{margin-left:-.75em;margin-right:-.75em}.ui.eight.doubling.cards .card{width:calc(25% - 1.5em);margin-left:.75em;margin-right:.75em}.ui.nine.doubling.cards{margin-left:-.75em;margin-right:-.75em}.ui.nine.doubling.cards .card{width:calc(25% - 1.5em);margin-left:.75em;margin-right:.75em}.ui.ten.doubling.cards{margin-left:-.75em;margin-right:-.75em}.ui.ten.doubling.cards .card{width:calc(20% - 1.5em);margin-left:.75em;margin-right:.75em}}@media only screen and (max-width:767px){.ui.stackable.cards{display:block!important}.ui.stackable.cards .card:first-child{margin-top:0!important}.ui.stackable.cards>.card{display:block!important;height:auto!important;margin:1em;padding:0!important;width:calc(100% - 2em)!important}}.ui.cards>.card{font-size:1em}.ui.comments{margin:1.5em 0;max-width:650px}.ui.comments:first-child{margin-top:0}.ui.comments:last-child{margin-bottom:0}.ui.comments .comment{position:relative;background:0 0;margin:.5em 0 0;padding:.5em 0 0;border:none;border-top:none;line-height:1.2}.ui.comments .comment:first-child{margin-top:0;padding-top:0}.ui.comments .comment .comments{margin:0 0 .5em .5em;padding:1em 0 1em 1em}.ui.comments .comment .comments:before{position:absolute;top:0;left:0}.ui.comments .comment .comments .comment{border:none;border-top:none;background:0 0}.ui.comments .comment .avatar{display:block;width:2.5em;height:auto;float:left;margin:.2em 0 0}.ui.comments .comment .avatar img,.ui.comments .comment img.avatar{display:block;margin:0 auto;width:100%;height:100%;border-radius:.25rem}.ui.comments .comment>.content{display:block}.ui.comments .comment>.avatar~.content{margin-left:3.5em}.ui.comments .comment .author{font-size:1em;color:rgba(0,0,0,.87);font-weight:700}.ui.comments .comment a.author{cursor:pointer}.ui.comments .comment a.author:hover{color:#1e70bf}.ui.comments .comment .metadata{display:inline-block;margin-left:.5em;color:rgba(0,0,0,.4);font-size:.875em}.ui.comments .comment .metadata>*{display:inline-block;margin:0 .5em 0 0}.ui.comments .comment .metadata>:last-child{margin-right:0}.ui.comments .comment .text{margin:.25em 0 .5em;font-size:1em;word-wrap:break-word;color:rgba(0,0,0,.87);line-height:1.3}.ui.comments .comment .actions{font-size:.875em}.ui.comments .comment .actions a{cursor:pointer;display:inline-block;margin:0 .75em 0 0;color:rgba(0,0,0,.4)}.ui.comments .comment .actions a:last-child{margin-right:0}.ui.comments .comment .actions a.active,.ui.comments .comment .actions a:hover{color:rgba(0,0,0,.8)}.ui.comments>.reply.form{margin-top:1em}.ui.comments .comment .reply.form{width:100%;margin-top:1em}.ui.comments .reply.form textarea{font-size:1em;height:12em}.ui.collapsed.comments,.ui.comments .collapsed.comment,.ui.comments .collapsed.comments{display:none}.ui.threaded.comments .comment .comments{margin:-1.5em 0 -1em 1.25em;padding:3em 0 2em 2.25em;box-shadow:-1px 0 0 rgba(34,36,38,.15)}.ui.minimal.comments .comment .actions{opacity:0;position:absolute;top:0;right:0;left:auto;-webkit-transition:opacity .2s ease;transition:opacity .2s ease;-webkit-transition-delay:.1s;transition-delay:.1s}.ui.minimal.comments .comment>.content:hover>.actions{opacity:1}.ui.mini.comments{font-size:.78571429rem}.ui.tiny.comments{font-size:.85714286rem}.ui.small.comments{font-size:.9em}.ui.comments{font-size:1em}.ui.large.comments{font-size:1.1em}.ui.big.comments{font-size:1.28571429rem}.ui.huge.comments{font-size:1.2em}.ui.massive.comments{font-size:1.71428571rem}.ui.feed{margin:1em 0}.ui.feed:first-child{margin-top:0}.ui.feed:last-child{margin-bottom:0}.ui.feed>.event{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;width:100%;padding:.21428571rem 0;margin:0;background:0 0;border-top:none}.ui.feed>.event:first-child{border-top:0;padding-top:0}.ui.feed>.event:last-child{padding-bottom:0}.ui.feed>.event>.label{display:block;-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:2.5em;height:auto;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;text-align:left}.ui.feed>.event>.label .icon{opacity:1;font-size:1.5em;width:100%;padding:.25em;background:0 0;border:none;border-radius:none;color:rgba(0,0,0,.6)}.ui.feed>.event>.label img{width:100%;height:auto;border-radius:500rem}.ui.feed>.event>.label+.content{margin:.5em 0 .35714286em 1.14285714em}.ui.feed>.event>.content{display:block;-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;text-align:left;word-wrap:break-word}.ui.feed>.event:last-child>.content{padding-bottom:0}.ui.feed>.event>.content a{cursor:pointer}.ui.feed>.event>.content .date{margin:-.5rem 0 0;padding:0;font-weight:400;font-size:1em;font-style:normal;color:rgba(0,0,0,.4)}.ui.feed>.event>.content .summary{margin:0;font-size:1em;font-weight:700;color:rgba(0,0,0,.87)}.ui.feed>.event>.content .summary img{display:inline-block;width:auto;height:10em;margin:-.25em .25em 0 0;border-radius:.25em;vertical-align:middle}.ui.feed>.event>.content .user{display:inline-block;font-weight:700;margin-right:0;vertical-align:baseline}.ui.feed>.event>.content .user img{margin:-.25em .25em 0 0;width:auto;height:10em;vertical-align:middle}.ui.feed>.event>.content .summary>.date{display:inline-block;float:none;font-weight:400;font-size:.85714286em;font-style:normal;margin:0 0 0 .5em;padding:0;color:rgba(0,0,0,.4)}.ui.feed>.event>.content .extra{margin:.5em 0 0;background:0 0;padding:0;color:rgba(0,0,0,.87)}.ui.feed>.event>.content .extra.images img{display:inline-block;margin:0 .25em 0 0;width:6em}.ui.feed>.event>.content .extra.text{padding:0;border-left:none;font-size:1em;max-width:500px;line-height:1.4285em}.ui.feed>.event>.content .meta{display:inline-block;font-size:.85714286em;margin:.5em 0 0;background:0 0;border:none;border-radius:0;box-shadow:none;padding:0;color:rgba(0,0,0,.6)}.ui.feed>.event>.content .meta>*{position:relative;margin-left:.75em}.ui.feed>.event>.content .meta>:after{content:'';color:rgba(0,0,0,.2);top:0;left:-1em;opacity:1;position:absolute;vertical-align:top}.ui.feed>.event>.content .meta .like{color:'';-webkit-transition:.2s color ease;transition:.2s color ease}.ui.feed>.event>.content .meta .like:hover .icon{color:#FF2733}.ui.feed>.event>.content .meta .active.like .icon{color:#EF404A}.ui.feed>.event>.content .meta>:first-child{margin-left:0}.ui.feed>.event>.content .meta>:first-child::after{display:none}.ui.feed>.event>.content .meta a,.ui.feed>.event>.content .meta>.icon{cursor:pointer;opacity:1;color:rgba(0,0,0,.5);-webkit-transition:color .1s ease;transition:color .1s ease}.ui.feed>.event>.content .meta a:hover,.ui.feed>.event>.content .meta a:hover .icon,.ui.feed>.event>.content .meta>.icon:hover{color:rgba(0,0,0,.95)}.ui.small.feed{font-size:.92857143rem}.ui.feed{font-size:1rem}.ui.large.feed{font-size:1.14285714rem}.ui.items>.item{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin:1em 0;width:100%;min-height:0;background:0 0;padding:0;border:none;border-radius:0;box-shadow:none;-webkit-transition:box-shadow .1s ease;transition:box-shadow .1s ease;z-index:''}.ui.items>.item a{cursor:pointer}.ui.items{margin:1.5em 0}.ui.items:first-child{margin-top:0!important}.ui.items:last-child{margin-bottom:0!important}.ui.items>.item:after{display:block;content:' ';height:0;clear:both;overflow:hidden;visibility:hidden}.ui.items>.item:first-child{margin-top:0}.ui.items>.item:last-child{margin-bottom:0}.ui.items>.item>.image{position:relative;-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;display:block;float:none;margin:0;padding:0;max-height:'';-webkit-align-self:top;-ms-flex-item-align:top;align-self:top}.ui.items>.item>.image>img{display:block;width:100%;height:auto;border-radius:.125rem;border:none}.ui.items>.item>.image:only-child>img{border-radius:0}.ui.items>.item>.content{display:block;-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;background:0 0;margin:0;padding:0;box-shadow:none;font-size:1em;border:none;border-radius:0}.ui.items>.item>.content:after{display:block;content:' ';height:0;clear:both;overflow:hidden;visibility:hidden}.ui.items>.item>.image+.content{min-width:0;width:auto;display:block;margin-left:0;-webkit-align-self:top;-ms-flex-item-align:top;align-self:top;padding-left:1.5em}.ui.items>.item>.content>.header{display:inline-block;margin:-.21425em 0 0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:700;color:rgba(0,0,0,.85)}.ui.items>.item>.content>.header:not(.ui){font-size:1.28571429em}.ui.items>.item [class*="left floated"]{float:left}.ui.items>.item [class*="right floated"]{float:right}.ui.items>.item .content img{-webkit-align-self:middle;-ms-flex-item-align:middle;align-self:middle;width:''}.ui.items>.item .avatar img,.ui.items>.item img.avatar{width:'';height:'';border-radius:500rem}.ui.items>.item>.content>.description{margin-top:.6em;max-width:auto;font-size:1em;line-height:1.4285em;color:rgba(0,0,0,.87)}.ui.items>.item>.content p{margin:0 0 .5em}.ui.items>.item>.content p:last-child{margin-bottom:0}.ui.items>.item .meta{margin:.5em 0;font-size:1em;line-height:1em;color:rgba(0,0,0,.6)}.ui.items>.item .meta *{margin-right:.3em}.ui.items>.item .meta :last-child{margin-right:0}.ui.items>.item .meta [class*="right floated"]{margin-right:0;margin-left:.3em}.ui.items>.item>.content a:not(.ui){color:'';-webkit-transition:color .1s ease;transition:color .1s ease}.ui.items>.item>.content a:not(.ui):hover{color:''}.ui.items>.item>.content>a.header{color:rgba(0,0,0,.85)}.ui.items>.item>.content>a.header:hover{color:#1e70bf}.ui.items>.item .meta>a:not(.ui){color:rgba(0,0,0,.4)}.ui.items>.item .meta>a:not(.ui):hover{color:rgba(0,0,0,.87)}.ui.items>.item>.content .favorite.icon{cursor:pointer;opacity:.75;-webkit-transition:color .1s ease;transition:color .1s ease}.ui.items>.item>.content .favorite.icon:hover{opacity:1;color:#FFB70A}.ui.items>.item>.content .active.favorite.icon{color:#FFE623}.ui.items>.item>.content .like.icon{cursor:pointer;opacity:.75;-webkit-transition:color .1s ease;transition:color .1s ease}.ui.items>.item>.content .like.icon:hover{opacity:1;color:#FF2733}.ui.items>.item>.content .active.like.icon{color:#FF2733}.ui.items>.item .extra{display:block;position:relative;background:0 0;margin:.5rem 0 0;width:100%;padding:0;top:0;left:0;color:rgba(0,0,0,.4);box-shadow:none;-webkit-transition:color .1s ease;transition:color .1s ease;border-top:none}.ui.items>.item .extra>*{margin:.25rem .5rem .25rem 0}.ui.items>.item .extra>[class*="right floated"]{margin:.25rem 0 .25rem .5rem}.ui.items>.item .extra:after{display:block;content:' ';height:0;clear:both;overflow:hidden;visibility:hidden}.ui.items>.item>.image:not(.ui){width:175px}@media only screen and (min-width:768px) and (max-width:991px){.ui.items>.item{margin:1em 0}.ui.items>.item>.image:not(.ui){width:150px}.ui.items>.item>.image+.content{display:block;padding:0 0 0 1em}}@media only screen and (max-width:767px){.ui.items>.item{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;margin:2em 0}.ui.items>.item>.image{display:block;margin-left:auto;margin-right:auto}.ui.items>.item>.image,.ui.items>.item>.image>img{max-width:100%!important;width:auto!important;max-height:250px!important}.ui.items>.item>.image+.content{display:block;padding:1.5em 0 0}}.ui.items>.item>.image+[class*="top aligned"].content{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.ui.items>.item>.image+[class*="middle aligned"].content{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.ui.items>.item>.image+[class*="bottom aligned"].content{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.ui.relaxed.items>.item{margin:1.5em 0}.ui[class*="very relaxed"].items>.item{margin:2em 0}.ui.divided.items>.item{border-top:1px solid rgba(34,36,38,.15);margin:0;padding:1em 0}.ui.divided.items>.item:first-child{border-top:none;margin-top:0!important;padding-top:0!important}.ui.divided.items>.item:last-child{margin-bottom:0!important;padding-bottom:0!important}.ui.relaxed.divided.items>.item{margin:0;padding:1.5em 0}.ui[class*="very relaxed"].divided.items>.item{margin:0;padding:2em 0}.ui.items a.item:hover,.ui.link.items>.item:hover{cursor:pointer}.ui.items a.item:hover .content .header,.ui.link.items>.item:hover .content .header{color:#1e70bf}.ui.items>.item{font-size:1em}.ui.statistic{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;margin:1em 0;max-width:auto}.ui.statistic+.ui.statistic{margin:0 0 0 1.5em}.ui.statistic:first-child{margin-top:0}.ui.statistic:last-child{margin-bottom:0}.ui.statistics{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.ui.statistics>.statistic{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;margin:0 1.5em 2em;max-width:auto}.ui.statistics{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin:1em -1.5em -2em}.ui.statistics:after{display:block;content:' ';height:0;clear:both;overflow:hidden;visibility:hidden}.ui.statistics:first-child{margin-top:0}.ui.statistics:last-child{margin-bottom:0}.ui.statistic>.value,.ui.statistics .statistic>.value{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:400;line-height:1em;color:#1B1C1D;text-transform:uppercase;text-align:center}.ui.statistic>.label,.ui.statistics .statistic>.label{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:1em;font-weight:700;color:rgba(0,0,0,.87);text-transform:uppercase;text-align:center}.ui.statistic>.label~.value,.ui.statistic>.value~.label,.ui.statistics .statistic>.label~.value,.ui.statistics .statistic>.value~.label{margin-top:0}.ui.statistic>.value .icon,.ui.statistics .statistic>.value .icon{opacity:1;width:auto;margin:0}.ui.statistic>.text.value,.ui.statistics .statistic>.text.value{line-height:1em;min-height:2em;font-weight:700;text-align:center}.ui.statistic>.text.value+.label,.ui.statistics .statistic>.text.value+.label{text-align:center}.ui.statistic>.value img,.ui.statistics .statistic>.value img{max-height:3rem;vertical-align:baseline}.ui.ten.statistics{margin:0 0 -2em}.ui.ten.statistics .statistic{min-width:10%;margin:0 0 2em}.ui.nine.statistics{margin:0 0 -2em}.ui.nine.statistics .statistic{min-width:11.11111111%;margin:0 0 2em}.ui.eight.statistics{margin:0 0 -2em}.ui.eight.statistics .statistic{min-width:12.5%;margin:0 0 2em}.ui.seven.statistics{margin:0 0 -2em}.ui.seven.statistics .statistic{min-width:14.28571429%;margin:0 0 2em}.ui.six.statistics{margin:0 0 -2em}.ui.six.statistics .statistic{min-width:16.66666667%;margin:0 0 2em}.ui.five.statistics{margin:0 0 -2em}.ui.five.statistics .statistic{min-width:20%;margin:0 0 2em}.ui.four.statistics{margin:0 0 -2em}.ui.four.statistics .statistic{min-width:25%;margin:0 0 2em}.ui.three.statistics{margin:0 0 -2em}.ui.three.statistics .statistic{min-width:33.33333333%;margin:0 0 2em}.ui.two.statistics{margin:0 0 -2em}.ui.two.statistics .statistic{min-width:50%;margin:0 0 2em}.ui.one.statistics{margin:0 0 -2em}.ui.one.statistics .statistic{min-width:100%;margin:0 0 2em}.ui.horizontal.statistic{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}.ui.horizontal.statistics{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;margin:0;max-width:none}.ui.horizontal.statistics .statistic{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;max-width:none;margin:1em 0}.ui.horizontal.statistic>.text.value,.ui.horizontal.statistics>.statistic>.text.value{min-height:0!important}.ui.horizontal.statistic>.value .icon,.ui.horizontal.statistics .statistic>.value .icon{width:1.18em}.ui.horizontal.statistic>.label,.ui.horizontal.statistics .statistic>.label{display:inline-block;vertical-align:middle;margin:0 0 0 .75em}.ui.red.statistic>.value,.ui.red.statistics .statistic>.value,.ui.statistics .red.statistic>.value{color:#DB2828}.ui.orange.statistic>.value,.ui.orange.statistics .statistic>.value,.ui.statistics .orange.statistic>.value{color:#F2711C}.ui.statistics .yellow.statistic>.value,.ui.yellow.statistic>.value,.ui.yellow.statistics .statistic>.value{color:#FBBD08}.ui.olive.statistic>.value,.ui.olive.statistics .statistic>.value,.ui.statistics .olive.statistic>.value{color:#B5CC18}.ui.green.statistic>.value,.ui.green.statistics .statistic>.value,.ui.statistics .green.statistic>.value{color:#21BA45}.ui.statistics .teal.statistic>.value,.ui.teal.statistic>.value,.ui.teal.statistics .statistic>.value{color:#00B5AD}.ui.blue.statistic>.value,.ui.blue.statistics .statistic>.value,.ui.statistics .blue.statistic>.value{color:#2185D0}.ui.statistics .violet.statistic>.value,.ui.violet.statistic>.value,.ui.violet.statistics .statistic>.value{color:#6435C9}.ui.purple.statistic>.value,.ui.purple.statistics .statistic>.value,.ui.statistics .purple.statistic>.value{color:#A333C8}.ui.pink.statistic>.value,.ui.pink.statistics .statistic>.value,.ui.statistics .pink.statistic>.value{color:#E03997}.ui.brown.statistic>.value,.ui.brown.statistics .statistic>.value,.ui.statistics .brown.statistic>.value{color:#A5673F}.ui.grey.statistic>.value,.ui.grey.statistics .statistic>.value,.ui.statistics .grey.statistic>.value{color:#767676}.ui.inverted.statistic .value,.ui.inverted.statistics .statistic>.value{color:#FFF}.ui.inverted.statistic .label,.ui.inverted.statistics .statistic>.label{color:rgba(255,255,255,.9)}.ui.inverted.red.statistic>.value,.ui.inverted.red.statistics .statistic>.value,.ui.statistics .inverted.red.statistic>.value{color:#FF695E}.ui.inverted.orange.statistic>.value,.ui.inverted.orange.statistics .statistic>.value,.ui.statistics .inverted.orange.statistic>.value{color:#FF851B}.ui.inverted.yellow.statistic>.value,.ui.inverted.yellow.statistics .statistic>.value,.ui.statistics .inverted.yellow.statistic>.value{color:#FFE21F}.ui.inverted.olive.statistic>.value,.ui.inverted.olive.statistics .statistic>.value,.ui.statistics .inverted.olive.statistic>.value{color:#D9E778}.ui.inverted.green.statistic>.value,.ui.inverted.green.statistics .statistic>.value,.ui.statistics .inverted.green.statistic>.value{color:#2ECC40}.ui.inverted.teal.statistic>.value,.ui.inverted.teal.statistics .statistic>.value,.ui.statistics .inverted.teal.statistic>.value{color:#6DFFFF}.ui.inverted.blue.statistic>.value,.ui.inverted.blue.statistics .statistic>.value,.ui.statistics .inverted.blue.statistic>.value{color:#54C8FF}.ui.inverted.violet.statistic>.value,.ui.inverted.violet.statistics .statistic>.value,.ui.statistics .inverted.violet.statistic>.value{color:#A291FB}.ui.inverted.purple.statistic>.value,.ui.inverted.purple.statistics .statistic>.value,.ui.statistics .inverted.purple.statistic>.value{color:#DC73FF}.ui.inverted.pink.statistic>.value,.ui.inverted.pink.statistics .statistic>.value,.ui.statistics .inverted.pink.statistic>.value{color:#FF8EDF}.ui.inverted.brown.statistic>.value,.ui.inverted.brown.statistics .statistic>.value,.ui.statistics .inverted.brown.statistic>.value{color:#D67C1C}.ui.inverted.grey.statistic>.value,.ui.inverted.grey.statistics .statistic>.value,.ui.statistics .inverted.grey.statistic>.value{color:#DCDDDE}.ui[class*="left floated"].statistic{float:left;margin:0 2em 1em 0}.ui[class*="right floated"].statistic{float:right;margin:0 0 1em 2em}.ui.floated.statistic:last-child{margin-bottom:0}.ui.mini.horizontal.statistic>.value,.ui.mini.horizontal.statistics .statistic>.value,.ui.mini.statistic>.value,.ui.mini.statistics .statistic>.value{font-size:1.5rem!important}.ui.mini.statistic>.text.value,.ui.mini.statistics .statistic>.text.value{font-size:1rem!important}.ui.tiny.horizontal.statistic>.value,.ui.tiny.horizontal.statistics .statistic>.value,.ui.tiny.statistic>.value,.ui.tiny.statistics .statistic>.value{font-size:2rem!important}.ui.tiny.statistic>.text.value,.ui.tiny.statistics .statistic>.text.value{font-size:1rem!important}.ui.small.statistic>.value,.ui.small.statistics .statistic>.value{font-size:3rem!important}.ui.small.horizontal.statistic>.value,.ui.small.horizontal.statistics .statistic>.value{font-size:2rem!important}.ui.small.statistic>.text.value,.ui.small.statistics .statistic>.text.value{font-size:1rem!important}.ui.statistic>.value,.ui.statistics .statistic>.value{font-size:4rem!important}.ui.horizontal.statistic>.value,.ui.horizontal.statistics .statistic>.value{display:inline-block;vertical-align:middle;font-size:3rem!important}.ui.statistic>.text.value,.ui.statistics .statistic>.text.value{font-size:2rem!important}.ui.large.statistic>.value,.ui.large.statistics .statistic>.value{font-size:5rem!important}.ui.large.horizontal.statistic>.value,.ui.large.horizontal.statistics .statistic>.value{font-size:4rem!important}.ui.large.statistic>.text.value,.ui.large.statistics .statistic>.text.value{font-size:2.5rem!important}.ui.huge.statistic>.value,.ui.huge.statistics .statistic>.value{font-size:6rem!important}.ui.huge.horizontal.statistic>.value,.ui.huge.horizontal.statistics .statistic>.value{font-size:5rem!important}.ui.huge.statistic>.text.value,.ui.huge.statistics .statistic>.text.value{font-size:2.5rem!important}.ui.accordion,.ui.accordion .accordion{max-width:100%}.ui.accordion .accordion{margin:1em 0 0;padding:0}.ui.accordion .accordion .title,.ui.accordion .title{cursor:pointer}.ui.accordion .title:not(.ui){padding:.5em 0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:1em;color:rgba(0,0,0,.87)}.ui.accordion .accordion .title~.content,.ui.accordion .title~.content{display:none}.ui.accordion:not(.styled) .accordion .title~.content:not(.ui),.ui.accordion:not(.styled) .title~.content:not(.ui){margin:'';padding:.5em 0 1em}.ui.accordion:not(.styled) .title~.content:not(.ui):last-child{padding-bottom:0}.ui.accordion .accordion .title .dropdown.icon,.ui.accordion .title .dropdown.icon{display:inline-block;float:none;opacity:1;width:1.25em;height:1em;margin:0 .25rem 0 0;padding:0;font-size:1em;-webkit-transition:opacity .1s ease,-webkit-transform .1s ease;transition:opacity .1s ease,-webkit-transform .1s ease;transition:transform .1s ease,opacity .1s ease;transition:transform .1s ease,opacity .1s ease,-webkit-transform .1s ease;vertical-align:baseline;-webkit-transform:none;transform:none}.ui.accordion.menu .item .title{display:block;padding:0}.ui.accordion.menu .item .title>.dropdown.icon{float:right;margin:.21425em 0 0 1em;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.ui.accordion .ui.header .dropdown.icon{font-size:1em;margin:0 .25rem 0 0}.ui.accordion .accordion .active.title .dropdown.icon,.ui.accordion .active.title .dropdown.icon,.ui.accordion.menu .item .active.title>.dropdown.icon{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.ui.styled.accordion{width:600px}.ui.styled.accordion,.ui.styled.accordion .accordion{border-radius:.28571429rem;background:#FFF;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15)}.ui.styled.accordion .accordion .title,.ui.styled.accordion .title{margin:0;padding:.75em 1em;color:rgba(0,0,0,.4);font-weight:700;border-top:1px solid rgba(34,36,38,.15);-webkit-transition:background .1s ease,color .1s ease;transition:background .1s ease,color .1s ease}.ui.styled.accordion .accordion .title:first-child,.ui.styled.accordion>.title:first-child{border-top:none}.ui.styled.accordion .accordion .content,.ui.styled.accordion .content{margin:0;padding:.5em 1em 1.5em}.ui.styled.accordion .accordion .content{padding:.5em 1em 1.5em}.ui.styled.accordion .accordion .active.title,.ui.styled.accordion .accordion .title:hover,.ui.styled.accordion .active.title,.ui.styled.accordion .title:hover{background:0 0;color:rgba(0,0,0,.87)}.ui.styled.accordion .accordion .active.title,.ui.styled.accordion .active.title{background:0 0;color:rgba(0,0,0,.95)}.ui.accordion .accordion .active.content,.ui.accordion .active.content{display:block}.ui.fluid.accordion,.ui.fluid.accordion .accordion{width:100%}.ui.inverted.accordion .title:not(.ui){color:rgba(255,255,255,.9)}@font-face{font-family:Accordion;src:url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMggjB5AAAAC8AAAAYGNtYXAPfOIKAAABHAAAAExnYXNwAAAAEAAAAWgAAAAIZ2x5Zryj6HgAAAFwAAAAyGhlYWT/0IhHAAACOAAAADZoaGVhApkB5wAAAnAAAAAkaG10eAJuABIAAAKUAAAAGGxvY2EAjABWAAACrAAAAA5tYXhwAAgAFgAAArwAAAAgbmFtZfC1n04AAALcAAABPHBvc3QAAwAAAAAEGAAAACAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDZ//3//wAB/+MPKwADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQASAEkAtwFuABMAADc0PwE2FzYXFh0BFAcGJwYvASY1EgaABQgHBQYGBQcIBYAG2wcGfwcBAQcECf8IBAcBAQd/BgYAAAAAAQAAAEkApQFuABMAADcRNDc2MzIfARYVFA8BBiMiJyY1AAUGBwgFgAYGgAUIBwYFWwEACAUGBoAFCAcFgAYGBQcAAAABAAAAAQAAqWYls18PPPUACwIAAAAAAM/9o+4AAAAAz/2j7gAAAAAAtwFuAAAACAACAAAAAAAAAAEAAAHg/+AAAAIAAAAAAAC3AAEAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAQAAAAC3ABIAtwAAAAAAAAAKABQAHgBCAGQAAAABAAAABgAUAAEAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADAAAAAEAAAAAAAIADgBAAAEAAAAAAAMADAAiAAEAAAAAAAQADABOAAEAAAAAAAUAFgAMAAEAAAAAAAYABgAuAAEAAAAAAAoANABaAAMAAQQJAAEADAAAAAMAAQQJAAIADgBAAAMAAQQJAAMADAAiAAMAAQQJAAQADABOAAMAAQQJAAUAFgAMAAMAAQQJAAYADAA0AAMAAQQJAAoANABaAHIAYQB0AGkAbgBnAFYAZQByAHMAaQBvAG4AIAAxAC4AMAByAGEAdABpAG4AZ3JhdGluZwByAGEAdABpAG4AZwBSAGUAZwB1AGwAYQByAHIAYQB0AGkAbgBnAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('truetype'),url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AAASwAAoAAAAABGgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAAS0AAAEtFpovuE9TLzIAAAIkAAAAYAAAAGAIIweQY21hcAAAAoQAAABMAAAATA984gpnYXNwAAAC0AAAAAgAAAAIAAAAEGhlYWQAAALYAAAANgAAADb/0IhHaGhlYQAAAxAAAAAkAAAAJAKZAedobXR4AAADNAAAABgAAAAYAm4AEm1heHAAAANMAAAABgAAAAYABlAAbmFtZQAAA1QAAAE8AAABPPC1n05wb3N0AAAEkAAAACAAAAAgAAMAAAEABAQAAQEBB3JhdGluZwABAgABADr4HAL4GwP4GAQeCgAZU/+Lix4KABlT/4uLDAeLa/iU+HQFHQAAAHkPHQAAAH4RHQAAAAkdAAABJBIABwEBBw0PERQZHnJhdGluZ3JhdGluZ3UwdTF1MjB1RjBEOXVGMERBAAACAYkABAAGAQEEBwoNVp38lA78lA78lA77lA773Z33bxWLkI2Qj44I9xT3FAWOj5CNkIuQi4+JjoePiI2Gi4YIi/uUBYuGiYeHiIiHh4mGi4aLho2Ijwj7FPcUBYeOiY+LkAgO+92L5hWL95QFi5CNkI6Oj4+PjZCLkIuQiY6HCPcU+xQFj4iNhouGi4aJh4eICPsU+xQFiIeGiYaLhouHjYePiI6Jj4uQCA74lBT4lBWLDAoAAAAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDZ//3//wAB/+MPKwADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAEAADfYOJZfDzz1AAsCAAAAAADP/aPuAAAAAM/9o+4AAAAAALcBbgAAAAgAAgAAAAAAAAABAAAB4P/gAAACAAAAAAAAtwABAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAEAAAAAtwASALcAAAAAUAAABgAAAAAADgCuAAEAAAAAAAEADAAAAAEAAAAAAAIADgBAAAEAAAAAAAMADAAiAAEAAAAAAAQADABOAAEAAAAAAAUAFgAMAAEAAAAAAAYABgAuAAEAAAAAAAoANABaAAMAAQQJAAEADAAAAAMAAQQJAAIADgBAAAMAAQQJAAMADAAiAAMAAQQJAAQADABOAAMAAQQJAAUAFgAMAAMAAQQJAAYADAA0AAMAAQQJAAoANABaAHIAYQB0AGkAbgBnAFYAZQByAHMAaQBvAG4AIAAxAC4AMAByAGEAdABpAG4AZ3JhdGluZwByAGEAdABpAG4AZwBSAGUAZwB1AGwAYQByAHIAYQB0AGkAbgBnAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff');font-weight:400;font-style:normal}.ui.accordion .accordion .title .dropdown.icon,.ui.accordion .title .dropdown.icon{font-family:Accordion;line-height:1;-webkit-backface-visibility:hidden;backface-visibility:hidden;font-weight:400;font-style:normal;text-align:center}.ui.accordion .accordion .title .dropdown.icon:before,.ui.accordion .title .dropdown.icon:before{content:'\f0da'}.ui.checkbox{position:relative;display:inline-block;-webkit-backface-visibility:hidden;backface-visibility:hidden;outline:0;vertical-align:baseline;font-style:normal;min-height:17px;font-size:1rem;line-height:17px;min-width:17px}.ui.checkbox input[type=checkbox],.ui.checkbox input[type=radio]{cursor:pointer;position:absolute;top:0;left:0;opacity:0!important;outline:0;z-index:3;width:17px;height:17px}.ui.checkbox .box,.ui.checkbox label{cursor:auto;position:relative;display:block;padding-left:1.85714em;outline:0;font-size:1em}.ui.checkbox .box:before,.ui.checkbox label:before{position:absolute;top:0;left:0;width:17px;height:17px;content:'';background:#FFF;border-radius:.21428571rem;-webkit-transition:border .1s ease,opacity .1s ease,box-shadow .1s ease,-webkit-transform .1s ease;transition:border .1s ease,opacity .1s ease,box-shadow .1s ease,-webkit-transform .1s ease;transition:border .1s ease,opacity .1s ease,transform .1s ease,box-shadow .1s ease;transition:border .1s ease,opacity .1s ease,transform .1s ease,box-shadow .1s ease,-webkit-transform .1s ease;border:1px solid #D4D4D5}.ui.checkbox .box:after,.ui.checkbox label:after{position:absolute;font-size:14px;top:0;left:0;width:17px;height:17px;text-align:center;opacity:0;color:rgba(0,0,0,.87);-webkit-transition:border .1s ease,opacity .1s ease,box-shadow .1s ease,-webkit-transform .1s ease;transition:border .1s ease,opacity .1s ease,box-shadow .1s ease,-webkit-transform .1s ease;transition:border .1s ease,opacity .1s ease,transform .1s ease,box-shadow .1s ease;transition:border .1s ease,opacity .1s ease,transform .1s ease,box-shadow .1s ease,-webkit-transform .1s ease;font-family:Checkbox}.ui.checkbox label,.ui.checkbox+label{color:rgba(0,0,0,.87);-webkit-transition:color .1s ease;transition:color .1s ease}.ui.checkbox+label{vertical-align:middle}.ui.checkbox .box:hover::before,.ui.checkbox label:hover::before{background:#FFF;border-color:rgba(34,36,38,.35)}.ui.checkbox label:hover,.ui.checkbox+label:hover{color:rgba(0,0,0,.8)}.ui.checkbox .box:active::before,.ui.checkbox label:active::before{background:#F9FAFB;border-color:rgba(34,36,38,.35)}.ui.checkbox .box:active::after,.ui.checkbox input:active~label,.ui.checkbox label:active::after{color:rgba(0,0,0,.95)}.ui.checkbox input:focus~.box:before,.ui.checkbox input:focus~label:before{background:#FFF;border-color:#96C8DA}.ui.checkbox input:focus~.box:after,.ui.checkbox input:focus~label,.ui.checkbox input:focus~label:after{color:rgba(0,0,0,.95)}.ui.checkbox input:checked~.box:before,.ui.checkbox input:checked~label:before{background:#FFF;border-color:rgba(34,36,38,.35)}.ui.checkbox input:checked~.box:after,.ui.checkbox input:checked~label:after{opacity:1;color:rgba(0,0,0,.95)}.ui.checkbox input:not([type=radio]):indeterminate~.box:before,.ui.checkbox input:not([type=radio]):indeterminate~label:before{background:#FFF;border-color:rgba(34,36,38,.35)}.ui.checkbox input:not([type=radio]):indeterminate~.box:after,.ui.checkbox input:not([type=radio]):indeterminate~label:after{opacity:1;color:rgba(0,0,0,.95)}.ui.checkbox input:checked:focus~.box:before,.ui.checkbox input:checked:focus~label:before,.ui.checkbox input:not([type=radio]):indeterminate:focus~.box:before,.ui.checkbox input:not([type=radio]):indeterminate:focus~label:before{background:#FFF;border-color:#96C8DA}.ui.checkbox input:checked:focus~.box:after,.ui.checkbox input:checked:focus~label:after,.ui.checkbox input:not([type=radio]):indeterminate:focus~.box:after,.ui.checkbox input:not([type=radio]):indeterminate:focus~label:after{color:rgba(0,0,0,.95)}.ui.read-only.checkbox,.ui.read-only.checkbox label{cursor:default}.ui.checkbox input[disabled]~.box:after,.ui.checkbox input[disabled]~label,.ui.disabled.checkbox .box:after,.ui.disabled.checkbox label{cursor:default!important;opacity:.5;color:#000}.ui.checkbox input.hidden{z-index:-1}.ui.checkbox input.hidden+label{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ui.radio.checkbox{min-height:15px}.ui.radio.checkbox .box,.ui.radio.checkbox label{padding-left:1.85714em}.ui.radio.checkbox .box:before,.ui.radio.checkbox label:before{content:'';-webkit-transform:none;transform:none;width:15px;height:15px;border-radius:500rem;top:1px;left:0}.ui.radio.checkbox .box:after,.ui.radio.checkbox label:after{border:none;content:''!important;line-height:15px;top:1px;left:0;width:15px;height:15px;border-radius:500rem;-webkit-transform:scale(.46666667);transform:scale(.46666667);background-color:rgba(0,0,0,.87)}.ui.radio.checkbox input:focus~.box:before,.ui.radio.checkbox input:focus~label:before{background-color:#FFF}.ui.radio.checkbox input:focus~.box:after,.ui.radio.checkbox input:focus~label:after{background-color:rgba(0,0,0,.95)}.ui.radio.checkbox input:indeterminate~.box:after,.ui.radio.checkbox input:indeterminate~label:after{opacity:0}.ui.radio.checkbox input:checked~.box:before,.ui.radio.checkbox input:checked~label:before{background-color:#FFF}.ui.radio.checkbox input:checked~.box:after,.ui.radio.checkbox input:checked~label:after{background-color:rgba(0,0,0,.95)}.ui.radio.checkbox input:focus:checked~.box:before,.ui.radio.checkbox input:focus:checked~label:before{background-color:#FFF}.ui.radio.checkbox input:focus:checked~.box:after,.ui.radio.checkbox input:focus:checked~label:after{background-color:rgba(0,0,0,.95)}.ui.slider.checkbox{min-height:1.25rem}.ui.slider.checkbox input{width:3.5rem;height:1.25rem}.ui.slider.checkbox .box,.ui.slider.checkbox label{padding-left:4.5rem;line-height:1rem;color:rgba(0,0,0,.4)}.ui.slider.checkbox .box:before,.ui.slider.checkbox label:before{display:block;position:absolute;content:'';border:none!important;left:0;z-index:1;top:.4rem;background-color:rgba(0,0,0,.05);width:3.5rem;height:.21428571rem;-webkit-transform:none;transform:none;border-radius:500rem;-webkit-transition:background .3s ease;transition:background .3s ease}.ui.slider.checkbox .box:after,.ui.slider.checkbox label:after{background:-webkit-linear-gradient(transparent,rgba(0,0,0,.05)) #FFF;background:linear-gradient(transparent,rgba(0,0,0,.05)) #FFF;position:absolute;content:''!important;opacity:1;z-index:2;border:none;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset;width:1.5rem;height:1.5rem;top:-.25rem;left:0;-webkit-transform:none;transform:none;border-radius:500rem;-webkit-transition:left .3s ease;transition:left .3s ease}.ui.slider.checkbox input:focus~.box:before,.ui.slider.checkbox input:focus~label:before{background-color:rgba(0,0,0,.15);border:none}.ui.slider.checkbox .box:hover,.ui.slider.checkbox label:hover{color:rgba(0,0,0,.8)}.ui.slider.checkbox .box:hover::before,.ui.slider.checkbox label:hover::before{background:rgba(0,0,0,.15)}.ui.slider.checkbox input:checked~.box,.ui.slider.checkbox input:checked~label{color:rgba(0,0,0,.95)!important}.ui.slider.checkbox input:checked~.box:before,.ui.slider.checkbox input:checked~label:before{background-color:#545454!important}.ui.slider.checkbox input:checked~.box:after,.ui.slider.checkbox input:checked~label:after{left:2rem}.ui.slider.checkbox input:focus:checked~.box,.ui.slider.checkbox input:focus:checked~label{color:rgba(0,0,0,.95)!important}.ui.slider.checkbox input:focus:checked~.box:before,.ui.slider.checkbox input:focus:checked~label:before{background-color:#000!important}.ui.toggle.checkbox{min-height:1.5rem}.ui.toggle.checkbox input{width:3.5rem;height:1.5rem}.ui.toggle.checkbox .box,.ui.toggle.checkbox label{min-height:1.5rem;padding-left:4.5rem;color:rgba(0,0,0,.87)}.ui.toggle.checkbox label{padding-top:.15em}.ui.toggle.checkbox .box:before,.ui.toggle.checkbox label:before{display:block;position:absolute;content:'';z-index:1;-webkit-transform:none;transform:none;border:none;top:0;background:rgba(0,0,0,.05);box-shadow:none;width:3.5rem;height:1.5rem;border-radius:500rem}.ui.toggle.checkbox .box:after,.ui.toggle.checkbox label:after{background:-webkit-linear-gradient(transparent,rgba(0,0,0,.05)) #FFF;background:linear-gradient(transparent,rgba(0,0,0,.05)) #FFF;position:absolute;content:''!important;opacity:1;z-index:2;border:none;box-shadow:0 1px 2px 0 rgba(34,36,38,.15),0 0 0 1px rgba(34,36,38,.15) inset;width:1.5rem;height:1.5rem;top:0;left:0;border-radius:500rem;-webkit-transition:background .3s ease,left .3s ease;transition:background .3s ease,left .3s ease}.ui.toggle.checkbox input~.box:after,.ui.toggle.checkbox input~label:after{left:-.05rem;box-shadow:none}.ui.toggle.checkbox .box:hover::before,.ui.toggle.checkbox input:focus~.box:before,.ui.toggle.checkbox input:focus~label:before,.ui.toggle.checkbox label:hover::before{background-color:rgba(0,0,0,.15);border:none}.ui.toggle.checkbox input:checked~.box,.ui.toggle.checkbox input:checked~label{color:rgba(0,0,0,.95)!important}.ui.toggle.checkbox input:checked~.box:before,.ui.toggle.checkbox input:checked~label:before{background-color:#2185D0!important}.ui.toggle.checkbox input:checked~.box:after,.ui.toggle.checkbox input:checked~label:after{left:2.15rem;box-shadow:none}.ui.toggle.checkbox input:focus:checked~.box,.ui.toggle.checkbox input:focus:checked~label{color:rgba(0,0,0,.95)!important}.ui.toggle.checkbox input:focus:checked~.box:before,.ui.toggle.checkbox input:focus:checked~label:before{background-color:#0d71bb!important}.ui.fitted.checkbox .box,.ui.fitted.checkbox label{padding-left:0!important}.ui.fitted.slider.checkbox,.ui.fitted.toggle.checkbox{width:3.5rem}@font-face{font-family:Checkbox;src:url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBD8AAAC8AAAAYGNtYXAYVtCJAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5Zn4huwUAAAF4AAABYGhlYWQGPe1ZAAAC2AAAADZoaGVhB30DyAAAAxAAAAAkaG10eBBKAEUAAAM0AAAAHGxvY2EAmgESAAADUAAAABBtYXhwAAkALwAAA2AAAAAgbmFtZSC8IugAAAOAAAABknBvc3QAAwAAAAAFFAAAACAAAwMTAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADoAgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6AL//f//AAAAAAAg6AD//f//AAH/4xgEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAEUAUQO7AvgAGgAAARQHAQYjIicBJjU0PwE2MzIfAQE2MzIfARYVA7sQ/hQQFhcQ/uMQEE4QFxcQqAF2EBcXEE4QAnMWEP4UEBABHRAXFhBOEBCoAXcQEE4QFwAAAAABAAABbgMlAkkAFAAAARUUBwYjISInJj0BNDc2MyEyFxYVAyUQEBf9SRcQEBAQFwK3FxAQAhJtFxAQEBAXbRcQEBAQFwAAAAABAAAASQMlA24ALAAAARUUBwYrARUUBwYrASInJj0BIyInJj0BNDc2OwE1NDc2OwEyFxYdATMyFxYVAyUQEBfuEBAXbhYQEO4XEBAQEBfuEBAWbhcQEO4XEBACEm0XEBDuFxAQEBAX7hAQF20XEBDuFxAQEBAX7hAQFwAAAQAAAAIAAHRSzT9fDzz1AAsEAAAAAADRsdR3AAAAANGx1HcAAAAAA7sDbgAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADuwABAAAAAAAAAAAAAAAAAAAABwQAAAAAAAAAAAAAAAIAAAAEAABFAyUAAAMlAAAAAAAAAAoAFAAeAE4AcgCwAAEAAAAHAC0AAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAIAAAAAQAAAAAAAgAHAGkAAQAAAAAAAwAIADkAAQAAAAAABAAIAH4AAQAAAAAABQALABgAAQAAAAAABgAIAFEAAQAAAAAACgAaAJYAAwABBAkAAQAQAAgAAwABBAkAAgAOAHAAAwABBAkAAwAQAEEAAwABBAkABAAQAIYAAwABBAkABQAWACMAAwABBAkABgAQAFkAAwABBAkACgA0ALBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhWZXJzaW9uIDIuMABWAGUAcgBzAGkAbwBuACAAMgAuADBDaGVja2JveABDAGgAZQBjAGsAYgBvAHhDaGVja2JveABDAGgAZQBjAGsAYgBvAHhSZWd1bGFyAFIAZQBnAHUAbABhAHJDaGVja2JveABDAGgAZQBjAGsAYgBvAHhGb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('truetype')}.ui.checkbox input:checked~.box:after,.ui.checkbox input:checked~label:after{content:'\e800'}.ui.checkbox input:indeterminate~.box:after,.ui.checkbox input:indeterminate~label:after{font-size:12px;content:'\e801'}.dimmable:not(.body){position:relative}.ui.dimmer{display:none;position:absolute;top:0!important;left:0!important;width:100%;height:100%;text-align:center;vertical-align:middle;background-color:rgba(0,0,0,.85);opacity:0;line-height:1;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-transition:background-color .5s linear;transition:background-color .5s linear;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;will-change:opacity;z-index:1000}.ui.dimmer>.content{width:100%;height:100%;display:table;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ui.dimmer>.content>*{display:table-cell;vertical-align:middle;color:#FFF}.ui.segment>.ui.dimmer{border-radius:inherit!important}.animating.dimmable:not(body),.dimmed.dimmable:not(body){overflow:hidden}.dimmed.dimmable>.ui.animating.dimmer,.dimmed.dimmable>.ui.visible.dimmer,.ui.active.dimmer{display:block;opacity:1}.ui.disabled.dimmer{width:0!important;height:0!important}.ui.page.dimmer{position:fixed;-webkit-transform-style:'';transform-style:'';-webkit-perspective:2000px;perspective:2000px;-webkit-transform-origin:center center;transform-origin:center center}body.animating.in.dimmable,body.dimmed.dimmable{overflow:hidden}body.dimmable>.dimmer{position:fixed}.blurring.dimmable>:not(.dimmer){-webkit-filter:blur(0) grayscale(0);filter:blur(0) grayscale(0);-webkit-transition:.8s filter ease;transition:.8s filter ease}.blurring.dimmed.dimmable>:not(.dimmer){-webkit-filter:blur(5px) grayscale(.7);filter:blur(5px) grayscale(.7)}.blurring.dimmable>.dimmer{background-color:rgba(0,0,0,.6)}.blurring.dimmable>.inverted.dimmer{background-color:rgba(255,255,255,.6)}.ui.dimmer>.top.aligned.content>*{vertical-align:top}.ui.dimmer>.bottom.aligned.content>*{vertical-align:bottom}.ui.inverted.dimmer{background-color:rgba(255,255,255,.85)}.ui.inverted.dimmer>.content>*{color:#FFF}.ui.simple.dimmer{display:block;overflow:hidden;opacity:1;width:0;height:0%;z-index:-100;background-color:rgba(0,0,0,0)}.dimmed.dimmable>.ui.simple.dimmer{overflow:visible;opacity:1;width:100%;height:100%;background-color:rgba(0,0,0,.85);z-index:1}.ui.simple.inverted.dimmer{background-color:rgba(255,255,255,0)}.dimmed.dimmable>.ui.simple.inverted.dimmer{background-color:rgba(255,255,255,.85)}.ui.dropdown{cursor:pointer;position:relative;display:inline-block;outline:0;text-align:left;-webkit-transition:box-shadow .1s ease,width .1s ease;transition:box-shadow .1s ease,width .1s ease;-webkit-tap-highlight-color:transparent}.ui.dropdown .menu{cursor:auto;position:absolute;display:none;outline:0;top:100%;min-width:-webkit-max-content;min-width:-moz-max-content;min-width:max-content;margin:0;padding:0;background:#FFF;font-size:1em;text-shadow:none;text-align:left;box-shadow:0 2px 3px 0 rgba(34,36,38,.15);border:1px solid rgba(34,36,38,.15);border-radius:.28571429rem;-webkit-transition:opacity .1s ease;transition:opacity .1s ease;z-index:11;will-change:transform,opacity}.ui.dropdown .menu>*{white-space:nowrap}.ui.dropdown>input:not(.search):first-child,.ui.dropdown>select{display:none!important}.ui.dropdown>.dropdown.icon{position:relative;font-size:.85714286em;margin:0 0 0 1em}.ui.dropdown .menu>.item .dropdown.icon{width:auto;float:right;margin:0 0 0 1em}.ui.dropdown .menu>.item .dropdown.icon+.text{margin-right:1em}.ui.dropdown>.text{display:inline-block;-webkit-transition:none;transition:none}.ui.dropdown .menu>.item{position:relative;cursor:pointer;display:block;border:none;height:auto;text-align:left;border-top:none;line-height:1em;color:rgba(0,0,0,.87);padding:.78571429rem 1.14285714rem!important;font-size:1rem;text-transform:none;font-weight:400;box-shadow:none;-webkit-touch-callout:none}.ui.dropdown .menu>.item:first-child{border-top-width:0}.ui.dropdown .menu .item>[class*="right floated"],.ui.dropdown>.text>[class*="right floated"]{float:right!important;margin-right:0!important;margin-left:1em!important}.ui.dropdown .menu .item>[class*="left floated"],.ui.dropdown>.text>[class*="left floated"]{float:left!important;margin-left:0!important;margin-right:1em!important}.ui.dropdown .menu .item>.flag.floated,.ui.dropdown .menu .item>.icon.floated,.ui.dropdown .menu .item>.image.floated,.ui.dropdown .menu .item>img.floated{margin-top:0}.ui.dropdown .menu>.header{margin:1rem 0 .75rem;padding:0 1.14285714rem;color:rgba(0,0,0,.85);font-size:.78571429em;font-weight:700;text-transform:uppercase}.ui.dropdown .menu>.divider{border-top:1px solid rgba(34,36,38,.1);height:0;margin:.5em 0}.ui.dropdown .menu>.input{width:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin:1.14285714rem .78571429rem;min-width:10rem}.ui.dropdown .menu>.header+.input{margin-top:0}.ui.dropdown .menu>.input:not(.transparent) input{padding:.5em 1em}.ui.dropdown .menu>.input:not(.transparent) .button,.ui.dropdown .menu>.input:not(.transparent) .icon,.ui.dropdown .menu>.input:not(.transparent) .label{padding-top:.5em;padding-bottom:.5em}.ui.dropdown .menu>.item>.description,.ui.dropdown>.text>.description{float:right;margin:0 0 0 1em;color:rgba(0,0,0,.4)}.ui.dropdown .menu>.message{padding:.78571429rem 1.14285714rem;font-weight:400}.ui.dropdown .menu>.message:not(.ui){color:rgba(0,0,0,.4)}.ui.dropdown .menu .menu{top:0!important;left:100%!important;right:auto!important;margin:0 0 0 -.5em!important;border-radius:.28571429rem!important;z-index:21!important}.ui.dropdown .menu .menu:after{display:none}.ui.dropdown .menu>.item>.flag,.ui.dropdown .menu>.item>.icon,.ui.dropdown .menu>.item>.image,.ui.dropdown .menu>.item>.label,.ui.dropdown .menu>.item>img,.ui.dropdown>.text>.flag,.ui.dropdown>.text>.icon,.ui.dropdown>.text>.image,.ui.dropdown>.text>.label,.ui.dropdown>.text>img{margin-top:0;margin-left:0;float:none;margin-right:.78571429rem}.ui.dropdown .menu>.item>.image,.ui.dropdown .menu>.item>img,.ui.dropdown>.text>.image,.ui.dropdown>.text>img{display:inline-block;vertical-align:middle;width:auto;max-height:2em}.ui.dropdown .ui.menu>.item:before,.ui.menu .ui.dropdown .menu>.item:before{display:none}.ui.menu .ui.dropdown .menu .active.item{border-left:none}.ui.buttons>.ui.dropdown:last-child .menu,.ui.menu .right.dropdown.item .menu,.ui.menu .right.menu .dropdown:last-child .menu{left:auto;right:0}.ui.label.dropdown .menu{min-width:100%}.ui.dropdown.icon.button>.dropdown.icon{margin:0}.ui.button.dropdown .menu{min-width:100%}.ui.selection.dropdown{cursor:pointer;word-wrap:break-word;line-height:1em;white-space:normal;outline:0;-webkit-transform:rotateZ(0);transform:rotateZ(0);min-width:14em;min-height:2.7142em;background:#FFF;display:inline-block;padding:.78571429em 2.1em .78571429em 1em;color:rgba(0,0,0,.87);box-shadow:none;border:1px solid rgba(34,36,38,.15);border-radius:.28571429rem;-webkit-transition:box-shadow .1s ease,width .1s ease;transition:box-shadow .1s ease,width .1s ease}.ui.selection.dropdown.active,.ui.selection.dropdown.visible{z-index:10}select.ui.dropdown{height:38px;padding:.5em;border:1px solid rgba(34,36,38,.15);visibility:visible}.ui.selection.dropdown>.delete.icon,.ui.selection.dropdown>.dropdown.icon,.ui.selection.dropdown>.search.icon{cursor:pointer;position:absolute;width:auto;height:auto;line-height:1.2142em;top:.78571429em;right:1em;z-index:3;margin:-.78571429em;padding:.78571429em;opacity:.8;-webkit-transition:opacity .1s ease;transition:opacity .1s ease}.ui.compact.selection.dropdown{min-width:0}.ui.selection.dropdown .menu{overflow-x:hidden;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-overflow-scrolling:touch;border-top-width:0!important;outline:0;margin:0 -1px;min-width:calc(100% + 2px);width:calc(100% + 2px);border-radius:0 0 .28571429rem .28571429rem;box-shadow:0 2px 3px 0 rgba(34,36,38,.15);-webkit-transition:opacity .1s ease;transition:opacity .1s ease}.ui.selection.dropdown .menu:after,.ui.selection.dropdown .menu:before{display:none}.ui.selection.dropdown .menu>.message{padding:.78571429rem 1.14285714rem}@media only screen and (max-width:767px){.ui.selection.dropdown .menu{max-height:8.01428571rem}}@media only screen and (min-width:768px){.ui.selection.dropdown .menu{max-height:10.68571429rem}}@media only screen and (min-width:992px){.ui.selection.dropdown .menu{max-height:16.02857143rem}}@media only screen and (min-width:1920px){.ui.selection.dropdown .menu{max-height:21.37142857rem}}.ui.selection.dropdown .menu>.item{border-top:1px solid #FAFAFA;padding:.78571429rem 1.14285714rem!important;white-space:normal;word-wrap:normal}.ui.selection.dropdown .menu>.hidden.addition.item{display:none}.ui.selection.dropdown:hover{border-color:rgba(34,36,38,.35);box-shadow:none}.ui.selection.active.dropdown,.ui.selection.active.dropdown .menu{border-color:#96C8DA;box-shadow:0 2px 3px 0 rgba(34,36,38,.15)}.ui.selection.dropdown:focus{border-color:#96C8DA;box-shadow:none}.ui.selection.dropdown:focus .menu{border-color:#96C8DA;box-shadow:0 2px 3px 0 rgba(34,36,38,.15)}.ui.selection.visible.dropdown>.text:not(.default){font-weight:400;color:rgba(0,0,0,.8)}.ui.selection.active.dropdown:hover,.ui.selection.active.dropdown:hover .menu{border-color:#96C8DA;box-shadow:0 2px 3px 0 rgba(34,36,38,.15)}.ui.active.selection.dropdown>.dropdown.icon,.ui.visible.selection.dropdown>.dropdown.icon{opacity:1;z-index:3}.ui.active.selection.dropdown{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}.ui.active.empty.selection.dropdown{border-radius:.28571429rem!important;box-shadow:none!important}.ui.active.empty.selection.dropdown .menu{border:none!important;box-shadow:none!important}.ui.search.dropdown{min-width:''}.ui.search.dropdown>input.search{background:none!important;border:none!important;box-shadow:none!important;cursor:text;top:0;left:1px;width:100%;outline:0;-webkit-tap-highlight-color:rgba(255,255,255,0);padding:inherit;position:absolute;z-index:2}.ui.search.dropdown>.text{cursor:text;position:relative;left:1px;z-index:3}.ui.search.selection.dropdown>input.search{line-height:1.2142em;padding:.67861429em 2.1em .67861429em 1em}.ui.search.selection.dropdown>span.sizer{line-height:1.2142em;padding:.67861429em 2.1em .67861429em 1em;display:none;white-space:pre}.ui.search.dropdown.active>input.search,.ui.search.dropdown.visible>input.search{cursor:auto}.ui.search.dropdown.active>.text,.ui.search.dropdown.visible>.text{pointer-events:none}.ui.active.search.dropdown input.search:focus+.text .flag,.ui.active.search.dropdown input.search:focus+.text .icon{opacity:.45}.ui.active.search.dropdown input.search:focus+.text{color:rgba(115,115,115,.87)!important}.ui.search.dropdown .menu{overflow-x:hidden;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-overflow-scrolling:touch}@media only screen and (max-width:767px){.ui.search.dropdown .menu{max-height:8.01428571rem}}@media only screen and (min-width:768px){.ui.search.dropdown .menu{max-height:10.68571429rem}}@media only screen and (min-width:992px){.ui.search.dropdown .menu{max-height:16.02857143rem}}@media only screen and (min-width:1920px){.ui.search.dropdown .menu{max-height:21.37142857rem}}.ui.multiple.dropdown{padding:.22620476em 2.1em .22620476em .35714286em}.ui.multiple.dropdown .menu{cursor:auto}.ui.multiple.search.dropdown,.ui.multiple.search.dropdown>input.search{cursor:text}.ui.multiple.dropdown>.label{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:inline-block;vertical-align:top;white-space:normal;font-size:1em;padding:.35714286em .78571429em;margin:.14285714rem .28571429rem .14285714rem 0;box-shadow:0 0 0 1px rgba(34,36,38,.15) inset}.ui.multiple.dropdown .dropdown.icon{margin:'';padding:''}.ui.multiple.dropdown>.text{position:static;padding:0;max-width:100%;margin:.45240952em 0 .45240952em .64285714em;line-height:1.21428571em}.ui.multiple.dropdown>.label~input.search{margin-left:.14285714em!important}.ui.multiple.dropdown>.label~.text{display:none}.ui.multiple.search.dropdown>.text{display:inline-block;position:absolute;top:0;left:0;padding:inherit;margin:.45240952em 0 .45240952em .64285714em;line-height:1.21428571em}.ui.multiple.search.dropdown>.label~.text{display:none}.ui.multiple.search.dropdown>input.search{position:static;padding:0;max-width:100%;margin:.45240952em 0 .45240952em .64285714em;width:2.2em;line-height:1.21428571em}.ui.inline.dropdown{cursor:pointer;display:inline-block;color:inherit}.ui.inline.dropdown .dropdown.icon{margin:0 .5em 0 .21428571em;vertical-align:baseline}.ui.inline.dropdown>.text{font-weight:700}.ui.inline.dropdown .menu{cursor:auto;margin-top:.21428571em;border-radius:.28571429rem}.ui.dropdown .menu .active.item{background:0 0;font-weight:700;color:rgba(0,0,0,.95);box-shadow:none;z-index:12}.ui.dropdown .menu>.item:hover{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95);z-index:13}.ui.loading.dropdown>i.icon{height:1em!important;padding:1.14285714em 1.07142857em!important}.ui.loading.dropdown>i.icon:before{position:absolute;content:'';top:50%;left:50%;margin:-.64285714em 0 0 -.64285714em;width:1.28571429em;height:1.28571429em;border-radius:500rem;border:.2em solid rgba(0,0,0,.1)}.ui.loading.dropdown>i.icon:after{position:absolute;content:'';top:50%;left:50%;box-shadow:0 0 0 1px transparent;margin:-.64285714em 0 0 -.64285714em;width:1.28571429em;height:1.28571429em;-webkit-animation:dropdown-spin .6s linear;animation:dropdown-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 transparent transparent;border-style:solid;border-width:.2em}.ui.loading.dropdown.button>i.icon:after,.ui.loading.dropdown.button>i.icon:before{display:none}@-webkit-keyframes dropdown-spin{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes dropdown-spin{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ui.default.dropdown:not(.button)>.text,.ui.dropdown:not(.button)>.default.text{color:rgba(191,191,191,.87)}.ui.default.dropdown:not(.button)>input:focus+.text,.ui.dropdown:not(.button)>input:focus+.default.text{color:rgba(115,115,115,.87)}.ui.loading.dropdown>.text{-webkit-transition:none;transition:none}.ui.dropdown .loading.menu{display:block;visibility:hidden;z-index:-1}.ui.dropdown .menu .selected.item,.ui.dropdown.selected{background:rgba(0,0,0,.03);color:rgba(0,0,0,.95)}.ui.dropdown>.filtered.text{visibility:hidden}.ui.dropdown .filtered.item{display:none!important}.ui.dropdown.error,.ui.dropdown.error>.default.text,.ui.dropdown.error>.text{color:#9F3A38}.ui.selection.dropdown.error{background:#FFF6F6;border-color:#E0B4B4}.ui.dropdown.error>.menu,.ui.dropdown.error>.menu .menu,.ui.selection.dropdown.error:hover{border-color:#E0B4B4}.ui.dropdown.error>.menu>.item{color:#9F3A38}.ui.multiple.selection.error.dropdown>.label{border-color:#E0B4B4}.ui.dropdown.error>.menu>.item:hover{background-color:#FFF2F2}.ui.dropdown.error>.menu .active.item{background-color:#FDCFCF}.ui.disabled.dropdown,.ui.dropdown .menu>.disabled.item{cursor:default;pointer-events:none;opacity:.45}.ui.dropdown .menu{left:0}.ui.dropdown .menu .right.menu,.ui.dropdown .right.menu>.menu{left:100%!important;right:auto!important;border-radius:.28571429rem!important}.ui.dropdown .menu .left.menu,.ui.dropdown>.left.menu .menu{left:auto!important;right:100%!important;border-radius:.28571429rem!important}.ui.dropdown .item .left.dropdown.icon,.ui.dropdown .left.menu .item .dropdown.icon{width:auto;float:left;margin:0 .78571429rem 0 0}.ui.dropdown .item .left.dropdown.icon+.text,.ui.dropdown .left.menu .item .dropdown.icon+.text{margin-left:1em}.ui.upward.dropdown>.menu{top:auto;bottom:100%;box-shadow:0 0 3px 0 rgba(0,0,0,.08);border-radius:.28571429rem .28571429rem 0 0}.ui.dropdown .upward.menu{top:auto!important;bottom:0!important}.ui.simple.upward.active.dropdown,.ui.simple.upward.dropdown:hover{border-radius:.28571429rem .28571429rem 0 0!important}.ui.upward.dropdown.button:not(.pointing):not(.floating).active{border-radius:.28571429rem .28571429rem 0 0}.ui.upward.selection.dropdown .menu{border-top-width:1px!important;border-bottom-width:0!important;box-shadow:0 -2px 3px 0 rgba(0,0,0,.08)}.ui.upward.selection.dropdown:hover{box-shadow:0 0 2px 0 rgba(0,0,0,.05)}.ui.active.upward.selection.dropdown{border-radius:0 0 .28571429rem .28571429rem!important}.ui.upward.selection.dropdown.visible{box-shadow:0 0 3px 0 rgba(0,0,0,.08);border-radius:0 0 .28571429rem .28571429rem!important}.ui.upward.active.selection.dropdown:hover{box-shadow:0 0 3px 0 rgba(0,0,0,.05)}.ui.upward.active.selection.dropdown:hover .menu{box-shadow:0 -2px 3px 0 rgba(0,0,0,.08)}.ui.dropdown .scrolling.menu,.ui.scrolling.dropdown .menu{overflow-x:hidden;overflow-y:auto}.ui.scrolling.dropdown .menu{overflow-x:hidden;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-overflow-scrolling:touch;min-width:100%!important;width:auto!important}.ui.dropdown .scrolling.menu{position:static;overflow-y:auto;border:none;box-shadow:none!important;border-radius:0!important;margin:0!important;min-width:100%!important;width:auto!important;border-top:1px solid rgba(34,36,38,.15)}.ui.dropdown .scrolling.menu>.item.item.item,.ui.scrolling.dropdown .menu .item.item.item{border-top:none;padding-right:calc(1.14285714rem + 17px)!important}.ui.dropdown .scrolling.menu .item:first-child,.ui.scrolling.dropdown .menu .item:first-child{border-top:none}.ui.dropdown>.animating.menu .scrolling.menu,.ui.dropdown>.visible.menu .scrolling.menu{display:block}@media all and (-ms-high-contrast:none){.ui.dropdown .scrolling.menu,.ui.scrolling.dropdown .menu{min-width:calc(100% - 17px)}}@media only screen and (max-width:767px){.ui.dropdown .scrolling.menu,.ui.scrolling.dropdown .menu{max-height:10.28571429rem}}.ui.simple.dropdown .menu:after,.ui.simple.dropdown .menu:before{display:none}.ui.simple.dropdown .menu{position:absolute;display:block;overflow:hidden;top:-9999px!important;opacity:0;width:0;height:0;-webkit-transition:opacity .1s ease;transition:opacity .1s ease}.ui.simple.active.dropdown,.ui.simple.dropdown:hover{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}.ui.simple.active.dropdown>.menu,.ui.simple.dropdown:hover>.menu{overflow:visible;width:auto;height:auto;top:100%!important;opacity:1}.ui.simple.dropdown:hover>.menu>.item:hover>.menu,.ui.simple.dropdown>.menu>.item:active>.menu{overflow:visible;width:auto;height:auto;top:0!important;left:100%!important;opacity:1}.ui.simple.disabled.dropdown:hover .menu{display:none;height:0;width:0;overflow:hidden}.ui.simple.visible.dropdown>.menu{display:block}.ui.fluid.dropdown{display:block;width:100%;min-width:0}.ui.fluid.dropdown>.dropdown.icon{float:right}.ui.floating.dropdown .menu{left:0;right:auto;box-shadow:0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.15)!important;border-radius:.28571429rem!important}.ui.floating.dropdown>.menu{margin-top:.5em!important;border-radius:.28571429rem!important}.ui.pointing.dropdown>.menu{top:100%;margin-top:.78571429rem;border-radius:.28571429rem}.ui.pointing.dropdown>.menu:after{display:block;position:absolute;pointer-events:none;content:'';visibility:visible;-webkit-transform:rotate(45deg);transform:rotate(45deg);width:.5em;height:.5em;box-shadow:-1px -1px 0 1px rgba(34,36,38,.15);background:#FFF;z-index:2;top:-.25em;left:50%;margin:0 0 0 -.25em}.ui.top.left.pointing.dropdown>.menu{top:100%;bottom:auto;left:0;right:auto;margin:1em 0 0}.ui.top.left.pointing.dropdown>.menu:after{top:-.25em;left:1em;right:auto;margin:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.ui.top.right.pointing.dropdown>.menu{top:100%;bottom:auto;right:0;left:auto;margin:1em 0 0}.ui.top.right.pointing.dropdown>.menu:after{top:-.25em;left:auto;right:1em;margin:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.ui.left.pointing.dropdown>.menu{top:0;left:100%;right:auto;margin:0 0 0 1em}.ui.left.pointing.dropdown>.menu:after{top:1em;left:-.25em;margin:0;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.ui.right.pointing.dropdown>.menu{top:0;left:auto;right:100%;margin:0 1em 0 0}.ui.right.pointing.dropdown>.menu:after{top:1em;left:auto;right:-.25em;margin:0;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.ui.bottom.pointing.dropdown>.menu{top:auto;bottom:100%;left:0;right:auto;margin:0 0 1em}.ui.bottom.pointing.dropdown>.menu:after{top:auto;bottom:-.25em;right:auto;margin:0;-webkit-transform:rotate(-135deg);transform:rotate(-135deg)}.ui.bottom.pointing.dropdown>.menu .menu{top:auto!important;bottom:0!important}.ui.bottom.left.pointing.dropdown>.menu{left:0;right:auto}.ui.bottom.left.pointing.dropdown>.menu:after{left:1em;right:auto}.ui.bottom.right.pointing.dropdown>.menu{right:0;left:auto}.ui.bottom.right.pointing.dropdown>.menu:after{left:auto;right:1em}.ui.upward.pointing.dropdown>.menu,.ui.upward.top.pointing.dropdown>.menu{top:auto;bottom:100%;margin:0 0 .78571429rem;border-radius:.28571429rem}.ui.upward.pointing.dropdown>.menu:after,.ui.upward.top.pointing.dropdown>.menu:after{top:100%;bottom:auto;box-shadow:1px 1px 0 1px rgba(34,36,38,.15);margin:-.25em 0 0}@font-face{font-family:Dropdown;src:url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMggjB5AAAAC8AAAAYGNtYXAPfuIIAAABHAAAAExnYXNwAAAAEAAAAWgAAAAIZ2x5Zjo82LgAAAFwAAABVGhlYWQAQ88bAAACxAAAADZoaGVhAwcB6QAAAvwAAAAkaG10eAS4ABIAAAMgAAAAIGxvY2EBNgDeAAADQAAAABJtYXhwAAoAFgAAA1QAAAAgbmFtZVcZpu4AAAN0AAABRXBvc3QAAwAAAAAEvAAAACAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDX//3//wAB/+MPLQADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAIABJQElABMAABM0NzY3BTYXFhUUDwEGJwYvASY1AAUGBwEACAUGBoAFCAcGgAUBEgcGBQEBAQcECQYHfwYBAQZ/BwYAAQAAAG4BJQESABMAADc0PwE2MzIfARYVFAcGIyEiJyY1AAWABgcIBYAGBgUI/wAHBgWABwaABQWABgcHBgUFBgcAAAABABIASQC3AW4AEwAANzQ/ATYXNhcWHQEUBwYnBi8BJjUSBoAFCAcFBgYFBwgFgAbbBwZ/BwEBBwQJ/wgEBwEBB38GBgAAAAABAAAASQClAW4AEwAANxE0NzYzMh8BFhUUDwEGIyInJjUABQYHCAWABgaABQgHBgVbAQAIBQYGgAUIBwWABgYFBwAAAAEAAAABAADZuaKOXw889QALAgAAAAAA0ABHWAAAAADQAEdYAAAAAAElAW4AAAAIAAIAAAAAAAAAAQAAAeD/4AAAAgAAAAAAASUAAQAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAABAAAAASUAAAElAAAAtwASALcAAAAAAAAACgAUAB4AQgBkAIgAqgAAAAEAAAAIABQAAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAOAAAAAQAAAAAAAgAOAEcAAQAAAAAAAwAOACQAAQAAAAAABAAOAFUAAQAAAAAABQAWAA4AAQAAAAAABgAHADIAAQAAAAAACgA0AGMAAwABBAkAAQAOAAAAAwABBAkAAgAOAEcAAwABBAkAAwAOACQAAwABBAkABAAOAFUAAwABBAkABQAWAA4AAwABBAkABgAOADkAAwABBAkACgA0AGMAaQBjAG8AbQBvAG8AbgBWAGUAcgBzAGkAbwBuACAAMQAuADAAaQBjAG8AbQBvAG8Abmljb21vb24AaQBjAG8AbQBvAG8AbgBSAGUAZwB1AGwAYQByAGkAYwBvAG0AbwBvAG4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format('truetype'),url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AAAVwAAoAAAAABSgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAAdkAAAHZLDXE/09TLzIAAALQAAAAYAAAAGAIIweQY21hcAAAAzAAAABMAAAATA9+4ghnYXNwAAADfAAAAAgAAAAIAAAAEGhlYWQAAAOEAAAANgAAADYAQ88baGhlYQAAA7wAAAAkAAAAJAMHAelobXR4AAAD4AAAACAAAAAgBLgAEm1heHAAAAQAAAAABgAAAAYACFAAbmFtZQAABAgAAAFFAAABRVcZpu5wb3N0AAAFUAAAACAAAAAgAAMAAAEABAQAAQEBCGljb21vb24AAQIAAQA6+BwC+BsD+BgEHgoAGVP/i4seCgAZU/+LiwwHi2v4lPh0BR0AAACIDx0AAACNER0AAAAJHQAAAdASAAkBAQgPERMWGyAlKmljb21vb25pY29tb29udTB1MXUyMHVGMEQ3dUYwRDh1RjBEOXVGMERBAAACAYkABgAIAgABAAQABwAKAA0AVgCfAOgBL/yUDvyUDvyUDvuUDvtvi/emFYuQjZCOjo+Pj42Qiwj3lIsFkIuQiY6Hj4iNhouGi4aJh4eHCPsU+xQFiIiGiYaLhouHjYeOCPsU9xQFiI+Jj4uQCA77b4v3FBWLkI2Pjo8I9xT3FAWPjo+NkIuQi5CJjogI9xT7FAWPh42Hi4aLhomHh4eIiIaJhosI+5SLBYaLh42HjoiPiY+LkAgO+92d928Vi5CNkI+OCPcU9xQFjo+QjZCLkIuPiY6Hj4iNhouGCIv7lAWLhomHh4iIh4eJhouGi4aNiI8I+xT3FAWHjomPi5AIDvvdi+YVi/eUBYuQjZCOjo+Pj42Qi5CLkImOhwj3FPsUBY+IjYaLhouGiYeHiAj7FPsUBYiHhomGi4aLh42Hj4iOiY+LkAgO+JQU+JQViwwKAAAAAAMCAAGQAAUAAAFMAWYAAABHAUwBZgAAAPUAGQCEAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA8NoB4P/g/+AB4AAgAAAAAQAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAA4AAAACgAIAAIAAgABACDw2v/9//8AAAAAACDw1//9//8AAf/jDy0AAwABAAAAAAAAAAAAAAABAAH//wAPAAEAAAABAAA5emozXw889QALAgAAAAAA0ABHWAAAAADQAEdYAAAAAAElAW4AAAAIAAIAAAAAAAAAAQAAAeD/4AAAAgAAAAAAASUAAQAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAABAAAAASUAAAElAAAAtwASALcAAAAAUAAACAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIADgBHAAEAAAAAAAMADgAkAAEAAAAAAAQADgBVAAEAAAAAAAUAFgAOAAEAAAAAAAYABwAyAAEAAAAAAAoANABjAAMAAQQJAAEADgAAAAMAAQQJAAIADgBHAAMAAQQJAAMADgAkAAMAAQQJAAQADgBVAAMAAQQJAAUAFgAOAAMAAQQJAAYADgA5AAMAAQQJAAoANABjAGkAYwBvAG0AbwBvAG4AVgBlAHIAcwBpAG8AbgAgADEALgAwAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG4AUgBlAGcAdQBsAGEAcgBpAGMAbwBtAG8AbwBuAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff');font-weight:400;font-style:normal}.ui.dropdown>.dropdown.icon{font-family:Dropdown;line-height:1;height:1em;-webkit-backface-visibility:hidden;backface-visibility:hidden;font-weight:400;font-style:normal;text-align:center;width:auto}.ui.dropdown>.dropdown.icon:before{content:'\f0d7'}.ui.dropdown .menu .item .dropdown.icon:before{content:'\f0da'}.ui.dropdown .item .left.dropdown.icon:before,.ui.dropdown .left.menu .item .dropdown.icon:before{content:"\f0d9"}.ui.vertical.menu .dropdown.item>.dropdown.icon:before{content:"\f0da"}.ui.embed{position:relative;max-width:100%;height:0;overflow:hidden;background:#DCDDDE;padding-bottom:56.25%}.ui.embed embed,.ui.embed iframe,.ui.embed object{position:absolute;border:none;width:100%;height:100%;top:0;left:0;margin:0;padding:0}.ui.embed>.embed{display:none}.ui.embed>.placeholder{position:absolute;cursor:pointer;top:0;left:0;display:block;width:100%;height:100%;background-color:radial-gradient(transparent 45%,rgba(0,0,0,.3))}.ui.embed>.icon{cursor:pointer;position:absolute;top:0;left:0;width:100%;height:100%;z-index:2}.ui.embed>.icon:after{position:absolute;top:0;left:0;width:100%;height:100%;z-index:3;content:'';background:-webkit-radial-gradient(transparent 45%,rgba(0,0,0,.3));background:radial-gradient(transparent 45%,rgba(0,0,0,.3));opacity:.5;-webkit-transition:opacity .5s ease;transition:opacity .5s ease}.ui.embed>.icon:before{position:absolute;top:50%;left:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%);color:#FFF;font-size:6rem;text-shadow:0 2px 10px rgba(34,36,38,.2);-webkit-transition:opacity .5s ease,color .5s ease;transition:opacity .5s ease,color .5s ease;z-index:10}.ui.embed .icon:hover:after{background:-webkit-radial-gradient(transparent 45%,rgba(0,0,0,.3));background:radial-gradient(transparent 45%,rgba(0,0,0,.3));opacity:1}.ui.embed .icon:hover:before{color:#FFF}.ui.active.embed>.icon,.ui.active.embed>.placeholder{display:none}.ui.active.embed>.embed{display:block}.ui.square.embed{padding-bottom:100%}.ui[class*="4:3"].embed{padding-bottom:75%}.ui[class*="16:9"].embed{padding-bottom:56.25%}.ui[class*="21:9"].embed{padding-bottom:42.85714286%}.ui.modal{display:none;position:fixed;z-index:1001;top:50%;left:50%;text-align:left;background:#FFF;border:none;box-shadow:1px 3px 3px 0 rgba(0,0,0,.2),1px 3px 15px 2px rgba(0,0,0,.2);-webkit-transform-origin:50% 25%;transform-origin:50% 25%;border-radius:.28571429rem;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;will-change:top,left,margin,transform,opacity}.ui.modal>.icon:first-child+*,.ui.modal>:first-child:not(.icon){border-top-left-radius:.28571429rem;border-top-right-radius:.28571429rem}.ui.modal>:last-child{border-bottom-left-radius:.28571429rem;border-bottom-right-radius:.28571429rem}.ui.modal>.close{cursor:pointer;position:absolute;top:-2.5rem;right:-2.5rem;z-index:1;opacity:.8;font-size:1.25em;color:#FFF;width:2.25rem;height:2.25rem;padding:.625rem 0 0}.ui.modal>.close:hover{opacity:1}.ui.modal>.header{display:block;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;background:#FFF;margin:0;padding:1.25rem 1.5rem;box-shadow:none;color:rgba(0,0,0,.85);border-bottom:1px solid rgba(34,36,38,.15)}.ui.modal>.header:not(.ui){font-size:1.42857143rem;line-height:1.2857em;font-weight:700}.ui.modal>.content{display:block;width:100%;font-size:1em;line-height:1.4;padding:1.5rem;background:#FFF}.ui.modal>.image.content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.ui.modal>.content>.image{display:block;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;width:'';-webkit-align-self:top;-ms-flex-item-align:top;align-self:top}.ui.modal>[class*="top aligned"]{-webkit-align-self:top;-ms-flex-item-align:top;align-self:top}.ui.modal>[class*="middle aligned"]{-webkit-align-self:middle;-ms-flex-item-align:middle;align-self:middle}.ui.modal>[class*=stretched]{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch}.ui.modal>.content>.description{display:block;-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;min-width:0;-webkit-align-self:top;-ms-flex-item-align:top;align-self:top}.ui.modal>.content>.icon+.description,.ui.modal>.content>.image+.description{-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;min-width:'';width:auto;padding-left:2em}.ui.modal>.content>.image>i.icon{margin:0;opacity:1;width:auto;line-height:1;font-size:8rem}.ui.modal>.actions{background:#F9FAFB;padding:1rem;border-top:1px solid rgba(34,36,38,.15);text-align:right}.ui.modal .actions>.button{margin-left:.75em}@media only screen and (max-width:767px){.ui.modal{width:95%;margin:0 0 0 -47.5%}}@media only screen and (min-width:768px){.ui.dropdown .scrolling.menu,.ui.scrolling.dropdown .menu{max-height:15.42857143rem}.ui.modal{width:88%;margin:0 0 0 -44%}}@media only screen and (min-width:992px){.ui.dropdown .scrolling.menu,.ui.scrolling.dropdown .menu{max-height:20.57142857rem}.ui.modal{width:850px;margin:0 0 0 -425px}}@media only screen and (min-width:1200px){.ui.modal{width:900px;margin:0 0 0 -450px}}@media only screen and (min-width:1920px){.ui.dropdown .scrolling.menu,.ui.scrolling.dropdown .menu{max-height:20.57142857rem}.ui.modal{width:950px;margin:0 0 0 -475px}}@media only screen and (max-width:991px){.ui.modal>.header{padding-right:2.25rem}.ui.modal>.close{top:1.0535rem;right:1rem;color:rgba(0,0,0,.87)}}@media only screen and (max-width:767px){.ui.modal>.header{padding:.75rem 2.25rem .75rem 1rem!important}.ui.modal>.content{display:block;padding:1rem!important}.ui.modal>.close{top:.5rem!important;right:.5rem!important}.ui.modal .image.content{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.ui.modal .content>.image{display:block;max-width:100%;margin:0 auto!important;text-align:center;padding:0 0 1rem!important}.ui.modal>.content>.image>i.icon{font-size:5rem;text-align:center}.ui.modal .content>.description{display:block;width:100%!important;margin:0!important;padding:1rem 0!important;box-shadow:none}.ui.modal>.actions{padding:1rem 1rem 0!important}.ui.modal .actions>.button,.ui.modal .actions>.buttons{margin-bottom:1rem}}.ui.inverted.dimmer>.ui.modal{box-shadow:1px 3px 10px 2px rgba(0,0,0,.2)}.ui.basic.modal{background-color:transparent;border:none;border-radius:0;box-shadow:none!important;color:#FFF}.ui.basic.modal>.actions,.ui.basic.modal>.content,.ui.basic.modal>.header{background-color:transparent}.ui.basic.modal>.header{color:#FFF}.ui.basic.modal>.close{top:1rem;right:1.5rem}.ui.inverted.dimmer>.basic.modal{color:rgba(0,0,0,.87)}.ui.inverted.dimmer>.ui.basic.modal>.header{color:rgba(0,0,0,.85)}.ui.active.modal{display:block}.scrolling.dimmable.dimmed{overflow:hidden}.scrolling.dimmable.dimmed>.dimmer{overflow:auto;-webkit-overflow-scrolling:touch}.scrolling.dimmable>.dimmer{position:fixed}.modals.dimmer .ui.scrolling.modal{position:static!important;margin:3.5rem auto!important}.scrolling.undetached.dimmable.dimmed{overflow:auto;-webkit-overflow-scrolling:touch}.scrolling.undetached.dimmable.dimmed>.dimmer{overflow:hidden}.scrolling.undetached.dimmable .ui.scrolling.modal{position:absolute;left:50%;margin-top:3.5rem!important}.undetached.dimmable.dimmed>.pusher{z-index:auto}@media only screen and (max-width:991px){.ui.basic.modal>.close{color:#FFF}.modals.dimmer .ui.scrolling.modal{margin-top:1rem!important;margin-bottom:1rem!important}}.ui.fullscreen.modal{width:95%!important;left:2.5%!important;margin:1em auto}.ui.fullscreen.scrolling.modal{left:0!important}.ui.fullscreen.modal>.header{padding-right:2.25rem}.ui.fullscreen.modal>.close{top:1.0535rem;right:1rem;color:rgba(0,0,0,.87)}.ui.modal{font-size:1rem}.ui.small.modal>.header:not(.ui){font-size:1.3em}@media only screen and (max-width:767px){.ui.small.modal{width:95%;margin:0 0 0 -47.5%}}@media only screen and (min-width:768px){.ui.small.modal{width:70.4%;margin:0 0 0 -35.2%}}@media only screen and (min-width:992px){.ui.small.modal{width:680px;margin:0 0 0 -340px}}@media only screen and (min-width:1200px){.ui.small.modal{width:720px;margin:0 0 0 -360px}}@media only screen and (min-width:1920px){.ui.small.modal{width:760px;margin:0 0 0 -380px}}.ui.large.modal>.header{font-size:1.6em}@media only screen and (max-width:767px){.ui.large.modal{width:95%;margin:0 0 0 -47.5%}}@media only screen and (min-width:768px){.ui.large.modal{width:88%;margin:0 0 0 -44%}}@media only screen and (min-width:992px){.ui.large.modal{width:1020px;margin:0 0 0 -510px}}@media only screen and (min-width:1200px){.ui.large.modal{width:1080px;margin:0 0 0 -540px}}@media only screen and (min-width:1920px){.ui.large.modal{width:1140px;margin:0 0 0 -570px}}.ui.nag{display:none;opacity:.95;position:relative;top:0;left:0;z-index:999;min-height:0;width:100%;margin:0;padding:.75em 1em;background:#555;box-shadow:0 1px 2px 0 rgba(0,0,0,.2);font-size:1rem;text-align:center;color:rgba(0,0,0,.87);border-radius:0 0 .28571429rem .28571429rem;-webkit-transition:.2s background ease;transition:.2s background ease}a.ui.nag{cursor:pointer}.ui.nag>.title{display:inline-block;margin:0 .5em;color:#FFF}.ui.nag>.close.icon{cursor:pointer;opacity:.4;position:absolute;top:50%;right:1em;font-size:1em;margin:-.5em 0 0;color:#FFF;-webkit-transition:opacity .2s ease;transition:opacity .2s ease}.ui.nag:hover{background:#555;opacity:1}.ui.nag .close:hover{opacity:1}.ui.overlay.nag{position:absolute;display:block}.ui.fixed.nag{position:fixed}.ui.bottom.nag,.ui.bottom.nags{border-radius:.28571429rem .28571429rem 0 0;top:auto;bottom:0}.ui.inverted.nag,.ui.inverted.nags .nag{background-color:#F3F4F5;color:rgba(0,0,0,.85)}.ui.inverted.nag .close,.ui.inverted.nag .title,.ui.inverted.nags .nag .close,.ui.inverted.nags .nag .title{color:rgba(0,0,0,.4)}.ui.nags .nag{border-radius:0!important}.ui.nags .nag:last-child{border-radius:0 0 .28571429rem .28571429rem}.ui.bottom.nags .nag:last-child{border-radius:.28571429rem .28571429rem 0 0}.ui.popup{display:none;position:absolute;top:0;right:0;min-width:-webkit-min-content;min-width:-moz-min-content;min-width:min-content;z-index:1900;border:1px solid #D4D4D5;line-height:1.4285em;max-width:250px;background:#FFF;padding:.833em 1em;font-weight:400;font-style:normal;color:rgba(0,0,0,.87);border-radius:.28571429rem;box-shadow:0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.15)}.ui.popup>.header{padding:0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:1.14285714em;line-height:1.2;font-weight:700}.ui.popup>.header+.content{padding-top:.5em}.ui.popup:before{position:absolute;content:'';width:.71428571em;height:.71428571em;background:#FFF;-webkit-transform:rotate(45deg);transform:rotate(45deg);z-index:2;box-shadow:1px 1px 0 0 #bababc}[data-tooltip]{position:relative}[data-tooltip]:not([data-position]):before{top:auto;right:auto;bottom:100%;left:50%;background:#FFF;margin-left:-.07142857rem;margin-bottom:.14285714rem}[data-tooltip]:not([data-position]):after{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);bottom:100%;margin-bottom:.5em}[data-tooltip]:after,[data-tooltip]:before{pointer-events:none;visibility:hidden}[data-tooltip]:before{position:absolute;content:'';font-size:1rem;width:.71428571em;height:.71428571em;background:#FFF;z-index:2;box-shadow:1px 1px 0 0 #bababc;opacity:0;-webkit-transform:rotate(45deg) scale(0)!important;transform:rotate(45deg) scale(0)!important;-webkit-transform-origin:center top;transform-origin:center top;-webkit-transition:all .1s ease;transition:all .1s ease}[data-tooltip]:after{content:attr(data-tooltip);position:absolute;text-transform:none;text-align:left;white-space:nowrap;font-size:1rem;border:1px solid #D4D4D5;line-height:1.4285em;max-width:none;background:#FFF;padding:.833em 1em;font-weight:400;font-style:normal;color:rgba(0,0,0,.87);border-radius:.28571429rem;box-shadow:0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.15);z-index:1;opacity:1;-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-transition:all .1s ease;transition:all .1s ease}[data-tooltip]:hover:after,[data-tooltip]:hover:before{visibility:visible;pointer-events:auto}[data-tooltip]:hover:before{-webkit-transform:rotate(45deg) scale(1)!important;transform:rotate(45deg) scale(1)!important;opacity:1}[data-tooltip]:after,[data-tooltip][data-position="top center"]:after,[data-tooltip][data-position="bottom center"]:after{-webkit-transform:translateX(-50%) scale(0)!important;transform:translateX(-50%) scale(0)!important}[data-tooltip]:hover:after,[data-tooltip][data-position="bottom center"]:hover:after{-webkit-transform:translateX(-50%) scale(1)!important;transform:translateX(-50%) scale(1)!important}[data-tooltip][data-position="left center"]:after,[data-tooltip][data-position="right center"]:after{-webkit-transform:translateY(-50%) scale(0)!important;transform:translateY(-50%) scale(0)!important}[data-tooltip][data-position="left center"]:hover:after,[data-tooltip][data-position="right center"]:hover:after{-webkit-transform:translateY(-50%) scale(1)!important;transform:translateY(-50%) scale(1)!important}[data-tooltip][data-position="top left"]:after,[data-tooltip][data-position="top right"]:after,[data-tooltip][data-position="bottom left"]:after,[data-tooltip][data-position="bottom right"]:after{-webkit-transform:scale(0)!important;transform:scale(0)!important}[data-tooltip][data-position="top left"]:hover:after,[data-tooltip][data-position="top right"]:hover:after,[data-tooltip][data-position="bottom left"]:hover:after,[data-tooltip][data-position="bottom right"]:hover:after{-webkit-transform:scale(1)!important;transform:scale(1)!important}[data-tooltip][data-inverted]:before{box-shadow:none!important;background:#1B1C1D}[data-tooltip][data-inverted]:after{background:#1B1C1D;color:#FFF;border:none;box-shadow:none}[data-tooltip][data-inverted]:after .header{background-color:none;color:#FFF}[data-position="top center"][data-tooltip]:after{top:auto;right:auto;left:50%;bottom:100%;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin-bottom:.5em}[data-position="top center"][data-tooltip]:before{top:auto;right:auto;bottom:100%;left:50%;background:#FFF;margin-left:-.07142857rem;margin-bottom:.14285714rem}[data-position="top left"][data-tooltip]:after{top:auto;right:auto;left:0;bottom:100%;margin-bottom:.5em}[data-position="top left"][data-tooltip]:before{top:auto;right:auto;bottom:100%;left:1em;margin-left:-.07142857rem;margin-bottom:.14285714rem}[data-position="top right"][data-tooltip]:after{top:auto;left:auto;right:0;bottom:100%;margin-bottom:.5em}[data-position="top right"][data-tooltip]:before{top:auto;left:auto;bottom:100%;right:1em;margin-left:-.07142857rem;margin-bottom:.14285714rem}[data-position="bottom center"][data-tooltip]:after{bottom:auto;right:auto;left:50%;top:100%;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin-top:.5em}[data-position="bottom center"][data-tooltip]:before{bottom:auto;right:auto;top:100%;left:50%;margin-left:-.07142857rem;margin-top:.14285714rem}[data-position="bottom left"][data-tooltip]:after{left:0;top:100%;margin-top:.5em}[data-position="bottom left"][data-tooltip]:before{bottom:auto;right:auto;top:100%;left:1em;margin-left:-.07142857rem;margin-top:.14285714rem}[data-position="bottom right"][data-tooltip]:after{right:0;top:100%;margin-top:.5em}[data-position="bottom right"][data-tooltip]:before{bottom:auto;left:auto;top:100%;right:1em;margin-left:-.14285714rem;margin-top:.07142857rem}[data-position="left center"][data-tooltip]:after{right:100%;top:50%;margin-right:.5em;-webkit-transform:translateY(-50%);transform:translateY(-50%)}[data-position="right center"][data-tooltip]:after{left:100%;top:50%;margin-left:.5em;-webkit-transform:translateY(-50%);transform:translateY(-50%)}[data-position~=bottom][data-tooltip]:before{background:#FFF;box-shadow:-1px -1px 0 0 #bababc;-webkit-transform-origin:center bottom;transform-origin:center bottom}[data-position="left center"][data-tooltip]:before{right:100%;top:50%;margin-top:-.14285714rem;margin-right:-.07142857rem;background:#FFF;box-shadow:1px -1px 0 0 #bababc}[data-position="right center"][data-tooltip]:before{left:100%;top:50%;margin-top:-.07142857rem;margin-left:.14285714rem;background:#FFF;box-shadow:-1px 1px 0 0 #bababc}[data-position~=top][data-tooltip]:before{background:#FFF}[data-inverted][data-position~=bottom][data-tooltip]:before{background:#1B1C1D;box-shadow:-1px -1px 0 0 #bababc}[data-inverted][data-position="left center"][data-tooltip]:before{background:#1B1C1D;box-shadow:1px -1px 0 0 #bababc}[data-inverted][data-position="right center"][data-tooltip]:before{background:#1B1C1D;box-shadow:-1px 1px 0 0 #bababc}[data-inverted][data-position~=top][data-tooltip]:before{background:#1B1C1D}[data-position~=bottom][data-tooltip]:after{-webkit-transform-origin:center top;transform-origin:center top}[data-position="left center"][data-tooltip]:before{-webkit-transform-origin:top center;transform-origin:top center}[data-position="left center"][data-tooltip]:after,[data-position="right center"][data-tooltip]:before{-webkit-transform-origin:right center;transform-origin:right center}[data-position="right center"][data-tooltip]:after{-webkit-transform-origin:left center;transform-origin:left center}.ui.popup{margin:0}.ui.top.popup{margin:0 0 .71428571em}.ui.top.left.popup{-webkit-transform-origin:left bottom;transform-origin:left bottom}.ui.top.center.popup{-webkit-transform-origin:center bottom;transform-origin:center bottom}.ui.top.right.popup{-webkit-transform-origin:right bottom;transform-origin:right bottom}.ui.left.center.popup{margin:0 .71428571em 0 0;-webkit-transform-origin:right 50%;transform-origin:right 50%}.ui.right.center.popup{margin:0 0 0 .71428571em;-webkit-transform-origin:left 50%;transform-origin:left 50%}.ui.bottom.popup{margin:.71428571em 0 0}.ui.bottom.left.popup{-webkit-transform-origin:left top;transform-origin:left top}.ui.bottom.center.popup{-webkit-transform-origin:center top;transform-origin:center top}.ui.bottom.right.popup{-webkit-transform-origin:right top;transform-origin:right top;margin-right:0}.ui.bottom.center.popup:before{margin-left:-.30714286em;top:-.30714286em;left:50%;right:auto;bottom:auto;box-shadow:-1px -1px 0 0 #bababc}.ui.bottom.left.popup{margin-left:0}.ui.bottom.left.popup:before{top:-.30714286em;left:1em;right:auto;bottom:auto;margin-left:0;box-shadow:-1px -1px 0 0 #bababc}.ui.bottom.right.popup:before{top:-.30714286em;right:1em;bottom:auto;left:auto;margin-left:0;box-shadow:-1px -1px 0 0 #bababc}.ui.top.center.popup:before{top:auto;right:auto;bottom:-.30714286em;left:50%;margin-left:-.30714286em}.ui.top.left.popup{margin-left:0}.ui.top.left.popup:before{bottom:-.30714286em;left:1em;top:auto;right:auto;margin-left:0}.ui.top.right.popup{margin-right:0}.ui.top.right.popup:before{bottom:-.30714286em;right:1em;top:auto;left:auto;margin-left:0}.ui.left.center.popup:before{top:50%;right:-.30714286em;bottom:auto;left:auto;margin-top:-.30714286em;box-shadow:1px -1px 0 0 #bababc}.ui.right.center.popup:before{top:50%;left:-.30714286em;bottom:auto;right:auto;margin-top:-.30714286em;box-shadow:-1px 1px 0 0 #bababc}.ui.bottom.popup:before,.ui.left.center.popup:before,.ui.right.center.popup:before,.ui.top.popup:before{background:#FFF}.ui.inverted.bottom.popup:before,.ui.inverted.left.center.popup:before,.ui.inverted.right.center.popup:before,.ui.inverted.top.popup:before{background:#1B1C1D}.ui.popup>.ui.grid:not(.padded){width:calc(100% + 1.75rem);margin:-.7rem -.875rem}.ui.loading.popup{display:block;visibility:hidden;z-index:-1}.ui.animating.popup,.ui.visible.popup{display:block}.ui.visible.popup{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden}.ui.basic.popup:before{display:none}.ui.wide.popup{max-width:350px}.ui[class*="very wide"].popup{max-width:550px}@media only screen and (max-width:767px){.ui.wide.popup,.ui[class*="very wide"].popup{max-width:250px}}.ui.fluid.popup{width:100%;max-width:none}.ui.inverted.popup{background:#1B1C1D;color:#FFF;border:none;box-shadow:none}.ui.inverted.popup .header{background-color:none;color:#FFF}.ui.inverted.popup:before{background-color:#1B1C1D;box-shadow:none!important}.ui.flowing.popup{max-width:none}.ui.mini.popup{font-size:.78571429rem}.ui.tiny.popup{font-size:.85714286rem}.ui.small.popup{font-size:.92857143rem}.ui.popup{font-size:1rem}.ui.large.popup{font-size:1.14285714rem}.ui.huge.popup{font-size:1.42857143rem}.ui.progress{position:relative;display:block;max-width:100%;border:none;margin:1em 0 2.5em;box-shadow:none;background:rgba(0,0,0,.1);padding:0;border-radius:.28571429rem}.ui.progress:first-child{margin:0 0 2.5em}.ui.progress:last-child{margin:0 0 1.5em}.ui.progress .bar{display:block;line-height:1;position:relative;width:0;min-width:2em;background:#888;border-radius:.28571429rem;-webkit-transition:width .1s ease,background-color .1s ease;transition:width .1s ease,background-color .1s ease}.ui.progress .bar>.progress{white-space:nowrap;position:absolute;width:auto;font-size:.92857143em;top:50%;right:.5em;left:auto;bottom:auto;color:rgba(255,255,255,.7);text-shadow:none;margin-top:-.5em;font-weight:700;text-align:left}.ui.progress>.label{position:absolute;width:100%;font-size:1em;top:100%;right:auto;left:0;bottom:auto;color:rgba(0,0,0,.87);font-weight:700;text-shadow:none;margin-top:.2em;text-align:center;-webkit-transition:color .4s ease;transition:color .4s ease}.ui.indicating.progress[data-percent^="1"] .bar,.ui.indicating.progress[data-percent^="2"] .bar{background-color:#D95C5C}.ui.indicating.progress[data-percent^="3"] .bar{background-color:#EFBC72}.ui.indicating.progress[data-percent^="4"] .bar,.ui.indicating.progress[data-percent^="5"] .bar{background-color:#E6BB48}.ui.indicating.progress[data-percent^="6"] .bar{background-color:#DDC928}.ui.indicating.progress[data-percent^="7"] .bar,.ui.indicating.progress[data-percent^="8"] .bar{background-color:#B4D95C}.ui.indicating.progress[data-percent^="9"] .bar,.ui.indicating.progress[data-percent^="100"] .bar{background-color:#66DA81}.ui.indicating.progress[data-percent^="1"] .label,.ui.indicating.progress[data-percent^="2"] .label,.ui.indicating.progress[data-percent^="3"] .label,.ui.indicating.progress[data-percent^="4"] .label,.ui.indicating.progress[data-percent^="5"] .label,.ui.indicating.progress[data-percent^="6"] .label,.ui.indicating.progress[data-percent^="7"] .label,.ui.indicating.progress[data-percent^="8"] .label,.ui.indicating.progress[data-percent^="9"] .label,.ui.indicating.progress[data-percent^="100"] .label{color:rgba(0,0,0,.87)}.ui.indicating.progress[data-percent="1"] .bar,.ui.indicating.progress[data-percent="2"] .bar,.ui.indicating.progress[data-percent="3"] .bar,.ui.indicating.progress[data-percent="4"] .bar,.ui.indicating.progress[data-percent="5"] .bar,.ui.indicating.progress[data-percent="6"] .bar,.ui.indicating.progress[data-percent="7"] .bar,.ui.indicating.progress[data-percent="8"] .bar,.ui.indicating.progress[data-percent="9"] .bar{background-color:#D95C5C}.ui.indicating.progress[data-percent="1"] .label,.ui.indicating.progress[data-percent="2"] .label,.ui.indicating.progress[data-percent="3"] .label,.ui.indicating.progress[data-percent="4"] .label,.ui.indicating.progress[data-percent="5"] .label,.ui.indicating.progress[data-percent="6"] .label,.ui.indicating.progress[data-percent="7"] .label,.ui.indicating.progress[data-percent="8"] .label,.ui.indicating.progress[data-percent="9"] .label{color:rgba(0,0,0,.87)}.ui.indicating.progress.success .label{color:#1A531B}.ui.progress.success .bar{background-color:#21BA45!important}.ui.progress.success .bar,.ui.progress.success .bar::after{-webkit-animation:none!important;animation:none!important}.ui.progress.success>.label{color:#1A531B}.ui.progress.warning .bar{background-color:#F2C037!important}.ui.progress.warning .bar,.ui.progress.warning .bar::after{-webkit-animation:none!important;animation:none!important}.ui.progress.warning>.label{color:#794B02}.ui.progress.error .bar{background-color:#DB2828!important}.ui.progress.error .bar,.ui.progress.error .bar::after{-webkit-animation:none!important;animation:none!important}.ui.progress.error>.label{color:#912D2B}.ui.active.progress .bar{position:relative;min-width:2em}.ui.active.progress .bar::after{content:'';opacity:0;position:absolute;top:0;left:0;right:0;bottom:0;background:#FFF;border-radius:.28571429rem;-webkit-animation:progress-active 2s ease infinite;animation:progress-active 2s ease infinite}@-webkit-keyframes progress-active{0%{opacity:.3;width:0}100%{opacity:0;width:100%}}@keyframes progress-active{0%{opacity:.3;width:0}100%{opacity:0;width:100%}}.ui.disabled.progress{opacity:.35}.ui.disabled.progress .bar,.ui.disabled.progress .bar::after{-webkit-animation:none!important;animation:none!important}.ui.inverted.progress{background:rgba(255,255,255,.08);border:none}.ui.inverted.progress .bar{background:#888}.ui.inverted.progress .bar>.progress{color:#F9FAFB}.ui.inverted.progress>.label{color:#FFF}.ui.inverted.progress.success>.label{color:#21BA45}.ui.inverted.progress.warning>.label{color:#F2C037}.ui.inverted.progress.error>.label{color:#DB2828}.ui.progress.attached{background:0 0;position:relative;border:none;margin:0}.ui.progress.attached,.ui.progress.attached .bar{display:block;height:.2rem;padding:0;overflow:hidden;border-radius:0 0 .28571429rem .28571429rem}.ui.progress.attached .bar{border-radius:0}.ui.progress.top.attached,.ui.progress.top.attached .bar{top:0;border-radius:.28571429rem .28571429rem 0 0}.ui.progress.top.attached .bar{border-radius:0}.ui.card>.ui.attached.progress,.ui.segment>.ui.attached.progress{position:absolute;top:auto;left:0;bottom:100%;width:100%}.ui.card>.ui.bottom.attached.progress,.ui.segment>.ui.bottom.attached.progress{top:100%;bottom:auto}.ui.red.progress .bar{background-color:#DB2828}.ui.red.inverted.progress .bar{background-color:#FF695E}.ui.orange.progress .bar{background-color:#F2711C}.ui.orange.inverted.progress .bar{background-color:#FF851B}.ui.yellow.progress .bar{background-color:#FBBD08}.ui.yellow.inverted.progress .bar{background-color:#FFE21F}.ui.olive.progress .bar{background-color:#B5CC18}.ui.olive.inverted.progress .bar{background-color:#D9E778}.ui.green.progress .bar{background-color:#21BA45}.ui.green.inverted.progress .bar{background-color:#2ECC40}.ui.teal.progress .bar{background-color:#00B5AD}.ui.teal.inverted.progress .bar{background-color:#6DFFFF}.ui.blue.progress .bar{background-color:#2185D0}.ui.blue.inverted.progress .bar{background-color:#54C8FF}.ui.violet.progress .bar{background-color:#6435C9}.ui.violet.inverted.progress .bar{background-color:#A291FB}.ui.purple.progress .bar{background-color:#A333C8}.ui.purple.inverted.progress .bar{background-color:#DC73FF}.ui.pink.progress .bar{background-color:#E03997}.ui.pink.inverted.progress .bar{background-color:#FF8EDF}.ui.brown.progress .bar{background-color:#A5673F}.ui.brown.inverted.progress .bar{background-color:#D67C1C}.ui.grey.progress .bar{background-color:#767676}.ui.grey.inverted.progress .bar{background-color:#DCDDDE}.ui.black.progress .bar{background-color:#1B1C1D}.ui.black.inverted.progress .bar{background-color:#545454}.ui.tiny.progress{font-size:.85714286rem}.ui.tiny.progress .bar{height:.5em}.ui.small.progress{font-size:.92857143rem}.ui.small.progress .bar{height:1em}.ui.progress{font-size:1rem}.ui.progress .bar{height:1.75em}.ui.large.progress{font-size:1.14285714rem}.ui.large.progress .bar{height:2.5em}.ui.big.progress{font-size:1.28571429rem}.ui.big.progress .bar{height:3.5em}.ui.rating:last-child{margin-right:0}.ui.rating .icon{padding:0;margin:0;-webkit-box-flex:1;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;cursor:pointer;width:1.25em;height:auto;-webkit-transition:opacity .1s ease,background .1s ease,text-shadow .1s ease,color .1s ease;transition:opacity .1s ease,background .1s ease,text-shadow .1s ease,color .1s ease;background:0 0;color:rgba(0,0,0,.15);font-family:Rating;line-height:1;-webkit-backface-visibility:hidden;backface-visibility:hidden;font-weight:400;font-style:normal;text-align:center}.ui.rating .active.icon{background:0 0;color:rgba(0,0,0,.85)}.ui.rating .icon.selected,.ui.rating .icon.selected.active{background:0 0;color:rgba(0,0,0,.87)}.ui.star.rating .icon{width:1.25em;height:auto;background:0 0;color:rgba(0,0,0,.15);text-shadow:none}.ui.star.rating .active.icon{background:0 0!important;color:#FFE623!important;text-shadow:0 -1px 0 #DDC507,-1px 0 0 #DDC507,0 1px 0 #DDC507,1px 0 0 #DDC507!important}.ui.star.rating .icon.selected,.ui.star.rating .icon.selected.active{background:0 0!important;color:#FC0!important;text-shadow:0 -1px 0 #E6A200,-1px 0 0 #E6A200,0 1px 0 #E6A200,1px 0 0 #E6A200!important}.ui.heart.rating .icon{width:1.4em;height:auto;background:0 0;color:rgba(0,0,0,.15);text-shadow:none!important}.ui.heart.rating .active.icon{background:0 0!important;color:#FF6D75!important;text-shadow:0 -1px 0 #CD0707,-1px 0 0 #CD0707,0 1px 0 #CD0707,1px 0 0 #CD0707!important}.ui.heart.rating .icon.selected,.ui.heart.rating .icon.selected.active{background:0 0!important;color:#FF3000!important;text-shadow:0 -1px 0 #AA0101,-1px 0 0 #AA0101,0 1px 0 #AA0101,1px 0 0 #AA0101!important}.ui.disabled.rating .icon{cursor:default}.ui.rating .icon.selected,.ui.rating.selected .active.icon,.ui.rating.selected .icon.selected{opacity:1}.ui.mini.rating{font-size:.78571429rem}.ui.tiny.rating{font-size:.85714286rem}.ui.small.rating{font-size:.92857143rem}.ui.rating{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;white-space:nowrap;vertical-align:baseline;font-size:1rem}.ui.large.rating{font-size:1.14285714rem}.ui.huge.rating{font-size:1.42857143rem}.ui.massive.rating{font-size:2rem}@font-face{font-family:Rating;src:url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMggjCBsAAAC8AAAAYGNtYXCj2pm8AAABHAAAAKRnYXNwAAAAEAAAAcAAAAAIZ2x5ZlJbXMYAAAHIAAARnGhlYWQBGAe5AAATZAAAADZoaGVhA+IB/QAAE5wAAAAkaG10eCzgAEMAABPAAAAAcGxvY2EwXCxOAAAUMAAAADptYXhwACIAnAAAFGwAAAAgbmFtZfC1n04AABSMAAABPHBvc3QAAwAAAAAVyAAAACAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADxZQHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEAJAAAAAgACAABAAAAAEAIOYF8AbwDfAj8C7wbvBw8Irwl/Cc8SPxZf/9//8AAAAAACDmAPAE8AzwI/Au8G7wcPCH8JfwnPEj8WT//f//AAH/4xoEEAYQAQ/sD+IPow+iD4wPgA98DvYOtgADAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAPAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAIAAP/tAgAB0wAKABUAAAEvAQ8BFwc3Fyc3BQc3Jz8BHwEHFycCALFPT7GAHp6eHoD/AHAWW304OH1bFnABGRqgoBp8sFNTsHyyOnxYEnFxElh8OgAAAAACAAD/7QIAAdMACgASAAABLwEPARcHNxcnNwUxER8BBxcnAgCxT0+xgB6enh6A/wA4fVsWcAEZGqCgGnywU1OwfLIBHXESWHw6AAAAAQAA/+0CAAHTAAoAAAEvAQ8BFwc3Fyc3AgCxT0+xgB6enh6AARkaoKAafLBTU7B8AAAAAAEAAAAAAgABwAArAAABFA4CBzEHDgMjIi4CLwEuAzU0PgIzMh4CFz4DMzIeAhUCAAcMEgugBgwMDAYGDAwMBqALEgwHFyg2HhAfGxkKChkbHxAeNigXAS0QHxsZCqAGCwkGBQkLBqAKGRsfEB42KBcHDBILCxIMBxcoNh4AAAAAAgAAAAACAAHAACsAWAAAATQuAiMiDgIHLgMjIg4CFRQeAhcxFx4DMzI+Aj8BPgM1DwEiFCIGMTAmIjQjJy4DNTQ+AjMyHgIfATc+AzMyHgIVFA4CBwIAFyg2HhAfGxkKChkbHxAeNigXBwwSC6AGDAwMBgYMDAwGoAsSDAdbogEBAQEBAaIGCgcEDRceEQkREA4GLy8GDhARCREeFw0EBwoGAS0eNigXBwwSCwsSDAcXKDYeEB8bGQqgBgsJBgUJCwagChkbHxA+ogEBAQGiBg4QEQkRHhcNBAcKBjQ0BgoHBA0XHhEJERAOBgABAAAAAAIAAcAAMQAAARQOAgcxBw4DIyIuAi8BLgM1ND4CMzIeAhcHFwc3Jzc+AzMyHgIVAgAHDBILoAYMDAwGBgwMDAagCxIMBxcoNh4KFRMSCC9wQLBwJwUJCgkFHjYoFwEtEB8bGQqgBgsJBgUJCwagChkbHxAeNigXAwUIBUtAoMBAOwECAQEXKDYeAAABAAAAAAIAAbcAKgAAEzQ3NjMyFxYXFhcWFzY3Njc2NzYzMhcWFRQPAQYjIi8BJicmJyYnJicmNQAkJUARExIQEAsMCgoMCxAQEhMRQCUkQbIGBwcGsgMFBQsKCQkGBwExPyMkBgYLCgkKCgoKCQoLBgYkIz8/QawFBawCBgUNDg4OFRQTAAAAAQAAAA0B2wHSACYAABM0PwI2FzYfAhYVFA8BFxQVFAcGByYvAQcGByYnJjU0PwEnJjUAEI9BBQkIBkCPEAdoGQMDBgUGgIEGBQYDAwEYaAcBIwsCFoEMAQEMgRYCCwYIZJABBQUFAwEBAkVFAgEBAwUFAwOQZAkFAAAAAAIAAAANAdsB0gAkAC4AABM0PwI2FzYfAhYVFA8BFxQVFAcmLwEHBgcmJyY1ND8BJyY1HwEHNxcnNy8BBwAQj0EFCQgGQI8QB2gZDAUGgIEGBQYDAwEYaAc/WBVsaxRXeDY2ASMLAhaBDAEBDIEWAgsGCGSQAQUNAQECRUUCAQEDBQUDA5BkCQURVXg4OHhVEW5uAAABACMAKQHdAXwAGgAANzQ/ATYXNh8BNzYXNh8BFhUUDwEGByYvASY1IwgmCAwLCFS8CAsMCCYICPUIDAsIjgjSCwkmCQEBCVS7CQEBCSYJCg0H9gcBAQePBwwAAAEAHwAfAXMBcwAsAAA3ND8BJyY1ND8BNjMyHwE3NjMyHwEWFRQPARcWFRQPAQYjIi8BBwYjIi8BJjUfCFRUCAgnCAwLCFRUCAwLCCcICFRUCAgnCAsMCFRUCAsMCCcIYgsIVFQIDAsIJwgIVFQICCcICwwIVFQICwwIJwgIVFQICCcIDAAAAAACAAAAJQFJAbcAHwArAAA3NTQ3NjsBNTQ3NjMyFxYdATMyFxYdARQHBiMhIicmNTczNTQnJiMiBwYdAQAICAsKJSY1NCYmCQsICAgIC/7tCwgIW5MWFR4fFRZApQsICDc0JiYmJjQ3CAgLpQsICAgIC8A3HhYVFRYeNwAAAQAAAAcBbgG3ACEAADcRNDc2NzYzITIXFhcWFREUBwYHBiMiLwEHBiMiJyYnJjUABgUKBgYBLAYGCgUGBgUKBQcOCn5+Cg4GBgoFBicBcAoICAMDAwMICAr+kAoICAQCCXl5CQIECAgKAAAAAwAAACUCAAFuABgAMQBKAAA3NDc2NzYzMhcWFxYVFAcGBwYjIicmJyY1MxYXFjMyNzY3JicWFRQHBiMiJyY1NDcGBzcUFxYzMjc2NTQ3NjMyNzY1NCcmIyIHBhUABihDREtLREMoBgYoQ0RLS0RDKAYlJjk5Q0M5OSYrQREmJTU1JSYRQSuEBAQGBgQEEREZBgQEBAQGJBkayQoKQSgoKChBCgoKCkEoJycoQQoKOiMjIyM6RCEeIjUmJSUmNSIeIUQlBgQEBAQGGBIRBAQGBgQEGhojAAAABQAAAAkCAAGJACwAOABRAGgAcAAANzQ3Njc2MzIXNzYzMhcWFxYXFhcWFxYVFDEGBwYPAQYjIicmNTQ3JicmJyY1MxYXNyYnJjU0NwYHNxQXFjMyNzY1NDc2MzI3NjU0JyYjIgcGFRc3Njc2NyYnNxYXFhcWFRQHBgcGBwYjPwEWFRQHBgcABitBQU0ZGhADBQEEBAUFBAUEBQEEHjw8Hg4DBQQiBQ0pIyIZBiUvSxYZDg4RQSuEBAQGBgQEEREZBgQEBAQGJBkaVxU9MzQiIDASGxkZEAYGCxQrODk/LlACFxYlyQsJQycnBRwEAgEDAwIDAwIBAwUCNmxsNhkFFAMFBBUTHh8nCQtKISgSHBsfIh4hRCUGBAQEBAYYEhEEBAYGBAQaGiPJJQUiIjYzISASGhkbCgoKChIXMRsbUZANCyghIA8AAAMAAAAAAbcB2wA5AEoAlAAANzU0NzY7ATY3Njc2NzY3Njc2MzIXFhcWFRQHMzIXFhUUBxYVFAcUFRQHFgcGKwEiJyYnJisBIicmNTcUFxYzMjc2NTQnJiMiBwYVFzMyFxYXFhcWFxYXFhcWOwEyNTQnNjc2NTQnNjU0JyYnNjc2NTQnJisBNDc2NTQnJiMGBwYHBgcGBwYHBgcGBwYHBgcGBwYrARUACwoQTgodEQ4GBAMFBgwLDxgTEwoKDjMdFhYOAgoRARkZKCUbGxsjIQZSEAoLJQUFCAcGBQUGBwgFBUkJBAUFBAQHBwMDBwcCPCUjNwIJBQUFDwMDBAkGBgsLDmUODgoJGwgDAwYFDAYQAQUGAwQGBgYFBgUGBgQJSbcPCwsGJhUPCBERExMMCgkJFBQhGxwWFR4ZFQoKFhMGBh0WKBcXBgcMDAoLDxIHBQYGBQcIBQYGBQgSAQEBAQICAQEDAgEULwgIBQoLCgsJDhQHCQkEAQ0NCg8LCxAdHREcDQ4IEBETEw0GFAEHBwUECAgFBQUFAgO3AAADAAD/2wG3AbcAPABNAJkAADc1NDc2OwEyNzY3NjsBMhcWBxUWFRQVFhUUBxYVFAcGKwEWFRQHBgcGIyInJicmJyYnJicmJyYnIyInJjU3FBcWMzI3NjU0JyYjIgcGFRczMhcWFxYXFhcWFxYXFhcWFxYXFhcWFzI3NjU0JyY1MzI3NjU0JyYjNjc2NTQnNjU0JyYnNjU0JyYrASIHIgcGBwYHBgcGIwYrARUACwoQUgYhJRsbHiAoGRkBEQoCDhYWHTMOCgoTExgPCwoFBgIBBAMFDhEdCk4QCgslBQUIBwYFBQYHCAUFSQkEBgYFBgUGBgYEAwYFARAGDAUGAwMIGwkKDg5lDgsLBgYJBAMDDwUFBQkCDg4ZJSU8AgcHAwMHBwQEBQUECbe3DwsKDAwHBhcWJwIWHQYGExYKChUZHhYVHRoiExQJCgsJDg4MDAwNBg4WJQcLCw+kBwUGBgUHCAUGBgUIpAMCBQYFBQcIBAUHBwITBwwTExERBw0OHBEdHRALCw8KDQ0FCQkHFA4JCwoLCgUICBgMCxUDAgEBAgMBAQG3AAAAAQAAAA0A7gHSABQAABM0PwI2FxEHBgcmJyY1ND8BJyY1ABCPQQUJgQYFBgMDARhoBwEjCwIWgQwB/oNFAgEBAwUFAwOQZAkFAAAAAAIAAAAAAgABtwAqAFkAABM0NzYzMhcWFxYXFhc2NzY3Njc2MzIXFhUUDwEGIyIvASYnJicmJyYnJjUzFB8BNzY1NCcmJyYnJicmIyIHBgcGBwYHBiMiJyYnJicmJyYjIgcGBwYHBgcGFQAkJUARExIQEAsMCgoMCxAQEhMRQCUkQbIGBwcGsgMFBQsKCQkGByU1pqY1BgYJCg4NDg0PDhIRDg8KCgcFCQkFBwoKDw4REg4PDQ4NDgoJBgYBMT8jJAYGCwoJCgoKCgkKCwYGJCM/P0GsBQWsAgYFDQ4ODhUUEzA1oJ82MBcSEgoLBgcCAgcHCwsKCQgHBwgJCgsLBwcCAgcGCwoSEhcAAAACAAAABwFuAbcAIQAoAAA3ETQ3Njc2MyEyFxYXFhURFAcGBwYjIi8BBwYjIicmJyY1PwEfAREhEQAGBQoGBgEsBgYKBQYGBQoFBw4Kfn4KDgYGCgUGJZIZef7cJwFwCggIAwMDAwgICv6QCggIBAIJeXkJAgQICAoIjRl0AWP+nQAAAAABAAAAJQHbAbcAMgAANzU0NzY7ATU0NzYzMhcWHQEUBwYrASInJj0BNCcmIyIHBh0BMzIXFh0BFAcGIyEiJyY1AAgIC8AmJjQ1JiUFBQgSCAUFFhUfHhUWHAsICAgIC/7tCwgIQKULCAg3NSUmJiU1SQgFBgYFCEkeFhUVFh43CAgLpQsICAgICwAAAAIAAQANAdsB0gAiAC0AABM2PwI2MzIfAhYXFg8BFxYHBiMiLwEHBiMiJyY/AScmNx8CLwE/AS8CEwEDDJBABggJBUGODgIDCmcYAgQCCAMIf4IFBgYEAgEZaQgC7hBbEgINSnkILgEBJggCFYILC4IVAggICWWPCgUFA0REAwUFCo9lCQipCTBmEw1HEhFc/u0AAAADAAAAAAHJAbcAFAAlAHkAADc1NDc2OwEyFxYdARQHBisBIicmNTcUFxYzMjc2NTQnJiMiBwYVFzU0NzYzNjc2NzY3Njc2NzY3Njc2NzY3NjMyFxYXFhcWFxYXFhUUFRQHBgcGBxQHBgcGBzMyFxYVFAcWFRYHFgcGBxYHBgcjIicmJyYnJiciJyY1AAUGB1MHBQYGBQdTBwYFJQUFCAcGBQUGBwgFBWQFBQgGDw8OFAkFBAQBAQMCAQIEBAYFBw4KCgcHBQQCAwEBAgMDAgYCAgIBAU8XEBAQBQEOBQUECwMREiYlExYXDAwWJAoHBQY3twcGBQUGB7cIBQUFBQgkBwYFBQYHCAUGBgUIJLcHBQYBEBATGQkFCQgGBQwLBgcICQUGAwMFBAcHBgYICQQEBwsLCwYGCgIDBAMCBBEQFhkSDAoVEhAREAsgFBUBBAUEBAcMAQUFCAAAAAADAAD/2wHJAZIAFAAlAHkAADcUFxYXNxY3Nj0BNCcmBycGBwYdATc0NzY3FhcWFRQHBicGJyY1FzU0NzY3Fjc2NzY3NjcXNhcWBxYXFgcWBxQHFhUUBwYHJxYXFhcWFRYXFhcWFRQVFAcGBwYHBgcGBwYnBicmJyYnJicmJyYnJicmJyYnJiciJyY1AAUGB1MHBQYGBQdTBwYFJQUFCAcGBQUGBwgFBWQGBQcKJBYMDBcWEyUmEhEDCwQFBQ4BBRAQEBdPAQECAgIGAgMDAgEBAwIEBQcHCgoOBwUGBAQCAQIDAQEEBAUJFA4PDwYIBQWlBwYFAQEBBwQJtQkEBwEBAQUGB7eTBwYEAQEEBgcJBAYBAQYECZS4BwYEAgENBwUCBgMBAQEXEyEJEhAREBcIDhAaFhEPAQEFAgQCBQELBQcKDAkIBAUHCgUGBwgDBgIEAQEHBQkIBwUMCwcECgcGCRoREQ8CBgQIAAAAAQAAAAEAAJth57dfDzz1AAsCAAAAAADP/GODAAAAAM/8Y4MAAP/bAgAB2wAAAAgAAgAAAAAAAAABAAAB4P/gAAACAAAAAAACAAABAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAEAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAdwAAAHcAAACAAAjAZMAHwFJAAABbgAAAgAAAAIAAAACAAAAAgAAAAEAAAACAAAAAW4AAAHcAAAB3AABAdwAAAHcAAAAAAAAAAoAFAAeAEoAcACKAMoBQAGIAcwCCgJUAoICxgMEAzoDpgRKBRgF7AYSBpgG2gcgB2oIGAjOAAAAAQAAABwAmgAFAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAwAAAABAAAAAAACAA4AQAABAAAAAAADAAwAIgABAAAAAAAEAAwATgABAAAAAAAFABYADAABAAAAAAAGAAYALgABAAAAAAAKADQAWgADAAEECQABAAwAAAADAAEECQACAA4AQAADAAEECQADAAwAIgADAAEECQAEAAwATgADAAEECQAFABYADAADAAEECQAGAAwANAADAAEECQAKADQAWgByAGEAdABpAG4AZwBWAGUAcgBzAGkAbwBuACAAMQAuADAAcgBhAHQAaQBuAGdyYXRpbmcAcgBhAHQAaQBuAGcAUgBlAGcAdQBsAGEAcgByAGEAdABpAG4AZwBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format('truetype'),url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AABcUAAoAAAAAFswAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAEuEAABLho6TvIE9TLzIAABPYAAAAYAAAAGAIIwgbY21hcAAAFDgAAACkAAAApKPambxnYXNwAAAU3AAAAAgAAAAIAAAAEGhlYWQAABTkAAAANgAAADYBGAe5aGhlYQAAFRwAAAAkAAAAJAPiAf1obXR4AAAVQAAAAHAAAABwLOAAQ21heHAAABWwAAAABgAAAAYAHFAAbmFtZQAAFbgAAAE8AAABPPC1n05wb3N0AAAW9AAAACAAAAAgAAMAAAEABAQAAQEBB3JhdGluZwABAgABADr4HAL4GwP4GAQeCgAZU/+Lix4KABlT/4uLDAeLZviU+HQFHQAAAP0PHQAAAQIRHQAAAAkdAAAS2BIAHQEBBw0PERQZHiMoLTI3PEFGS1BVWl9kaW5zeH2Ch4xyYXRpbmdyYXRpbmd1MHUxdTIwdUU2MDB1RTYwMXVFNjAydUU2MDN1RTYwNHVFNjA1dUYwMDR1RjAwNXVGMDA2dUYwMEN1RjAwRHVGMDIzdUYwMkV1RjA2RXVGMDcwdUYwODd1RjA4OHVGMDg5dUYwOEF1RjA5N3VGMDlDdUYxMjN1RjE2NHVGMTY1AAACAYkAGgAcAgABAAQABwAKAA0AVgCWAL0BAgGMAeQCbwLwA4cD5QR0BQMFdgZgB8MJkQtxC7oM2Q1jDggOmRAYEZr8lA78lA78lA77lA74lPetFftFpTz3NDz7NPtFcfcU+xBt+0T3Mt73Mjht90T3FPcQBfuU+0YV+wRRofcQMOP3EZ3D9wXD+wX3EXkwM6H7EPsExQUO+JT3rRX7RaU89zQ8+zT7RXH3FPsQbftE9zLe9zI4bfdE9xT3EAX7lPtGFYuLi/exw/sF9xF5MDOh+xD7BMUFDviU960V+0WlPPc0PPs0+0Vx9xT7EG37RPcy3vcyOG33RPcU9xAFDviU98EVi2B4ZG5wCIuL+zT7NAV7e3t7e4t7i3ube5sI+zT3NAVupniyi7aL3M3N3Iu2i7J4pm6mqLKetovci81JizoIDviU98EVi9xJzTqLYItkeHBucKhknmCLOotJSYs6i2CeZKhwCIuL9zT7NAWbe5t7m4ubi5ubm5sI9zT3NAWopp6yi7YIME0V+zb7NgWKioqKiouKi4qMiowI+zb3NgV6m4Ghi6OLubCwuYuji6GBm3oIule6vwWbnKGVo4u5i7Bmi12Lc4F1ensIDviU98EVi2B4ZG5wCIuL+zT7NAV7e3t7e4t7i3ube5sI+zT3NAVupniyi7aL3M3N3Iuni6WDoX4IXED3BEtL+zT3RPdU+wTLssYFl46YjZiL3IvNSYs6CA6L98UVi7WXrKOio6Otl7aLlouXiZiHl4eWhZaEloSUhZKFk4SShZKEkpKSkZOSkpGUkZaSCJaSlpGXj5iPl42Wi7aLrX+jc6N0l2qLYYthdWBgYAj7RvtABYeIh4mGi4aLh42Hjgj7RvdABYmNiY2Hj4iOhpGDlISUhZWFlIWVhpaHmYaYiZiLmAgOZ4v3txWLkpCPlo0I9yOgzPcWBY6SkI+Ri5CLkIePhAjL+xb3I3YFlomQh4uEi4aJh4aGCCMmpPsjBYuKi4mLiIuHioiJiImIiIqHi4iLh4yHjQj7FM/7FUcFh4mHioiLh4uIjImOiY6KjouPi4yLjYyOCKP3IyPwBYaQiZCLjwgOZ4v3txWLkpCPlo0I9yOgzPcWBY6SkI+Ri5CLkIePhAjL+xb3I3YFlomQh4uEi4aJh4aGCCMmpPsjBYuKi4mLiIuCh4aDi4iLh4yHjQj7FM/7FUcFh4mHioiLh4uIjImOiY6KjouPi4yLjYyOCKP3IyPwBYaQiZCLjwjKeRXjN3b7DfcAxPZSd/cN4t/7DJ1V9wFV+wEFDq73ZhWLk42RkZEIsbIFkZCRjpOLkouSiJCGCN8291D3UAWQkJKOkouTi5GIkYYIsWQFkYaNhIuEi4OJhYWFCPuJ+4kFhYWFiYOLhIuEjYaRCPsi9yIFhZCJkouSCA77AartFYuSjpKQkAjf3zffBYaQiJKLk4uSjpKQkAiysgWRkJGOk4uSi5KIkIYI3zff3wWQkJKOk4uSi5KIkIYIsmQFkIaOhIuEi4OIhIaGCDc33zcFkIaOhIuEi4OIhYaFCGRkBYaGhIiEi4OLhI6GkAg33zc3BYaGhIiEi4OLhY6FkAhksgWGkYiRi5MIDvtLi8sVi/c5BYuSjpKQkJCQko6SiwiVi4vCBYuul6mkpKSkqpiui66LqX6kcqRymG2LaAiLVJSLBZKLkoiQhpCGjoSLhAiL+zkFi4OIhYaGhoWEiYSLCPuniwWEi4SNhpGGkIiRi5MI5vdUFfcni4vCBYufhJx8mn2ZepJ3i3aLeoR9fX18g3qLdwiLVAUO+yaLshWL+AQFi5GNkY+RjpCQj5KNj42PjI+LCPfAiwWPi4+Kj4mRiZCHj4aPhY2Fi4UIi/wEBYuEiYWHhoeGhoeFiIiKhoqHi4GLhI6EkQj7EvcN+xL7DQWEhYOIgouHi4eLh42EjoaPiJCHkImRi5IIDov3XRWLko2Rj5Kltq+vuKW4pbuZvYu9i7t9uHG4ca9npWCPhI2Fi4SLhYmEh4RxYGdoXnAIXnFbflmLWYtbmF6lXqZnrnG2h5KJkouRCLCLFaRkq2yxdLF0tH+4i7iLtJexorGiq6qksm64Z61goZZ3kXaLdItnfm1ycnJybX9oiwhoi22XcqRypH6pi6+LopGglp9gdWdpbl4I9xiwFYuHjIiOiI6IjoqPi4+LjoyOjo2OjY6Lj4ubkJmXl5eWmZGbi4+LjoyOjo2OjY6LjwiLj4mOiY6IjYiNh4tzi3eCenp6eoJ3i3MIDov3XRWLko2Sj5GouK+utqW3pbqYvouci5yJnIgIm6cFjY6NjI+LjIuNi42JjYqOio+JjomOiY6KjomOiY6JjoqNioyKjomMiYuHi4qLiouLCHdnbVVjQ2NDbVV3Zwh9cgWJiIiJiIuJi36SdJiIjYmOi46LjY+UlJlvl3KcdJ90oHeie6WHkYmSi5IIsIsVqlq0Z711CKGzBXqXfpqCnoKdhp6LoIuikaCWn2B1Z2luXgj3GLAVi4eMiI6IjoiOio+Lj4uOjI6OjY6NjouPi5uQmZeXl5aZkZuLj4uOjI6OjY6NjouPCIuPiY6JjoiNiI2Hi3OLd4J6enp6gneLcwji+10VoLAFtI+wmK2hrqKnqKKvdq1wp2uhCJ2rBZ1/nHycepx6mHqWeY+EjYWLhIuEiYWHhIR/gH1+fG9qaXJmeWV5Y4Jhiwi53BXb9yQFjIKMg4uEi3CDc3x1fHV3fHOBCA6L1BWL90sFi5WPlJKSkpKTj5aLCNmLBZKPmJqepJaZlZeVlY+Qj5ONl42WjpeOmI+YkZWTk5OSk46Vi5uLmYiYhZiFlIGSfgiSfo55i3WLeYd5gXgIvosFn4uchJl8mn2Seot3i3qGfIJ9jYSLhYuEi3yIfoR+i4eLh4uHi3eGen99i3CDdnt8CHt8dYNwiwhmiwV5i3mNeY95kHeRc5N1k36Ph4sIOYsFgIuDjoSShJKHlIuVCLCdFYuGjIePiI+Hj4mQi5CLj42Pj46OjY+LkIuQiZCIjoePh42Gi4aLh4mHh4eIioaLhgjUeRWUiwWNi46Lj4qOi4+KjYqOi4+Kj4mQio6KjYqNio+Kj4mQio6KjIqzfquEpIsIrosFr4uemouri5CKkYqQkY6QkI6SjpKNkouSi5KJkoiRlZWQlouYi5CKkImRiZGJj4iOCJGMkI+PlI+UjZKLkouViJODk4SSgo+CiwgmiwWLlpCalJ6UnpCbi5aLnoiYhJSFlH+QeYuGhoeDiYCJf4h/h3+IfoWBg4KHh4SCgH4Ii4qIiYiGh4aIh4mIiIiIh4eGh4aHh4eHiIiHiIeHiIiHiIeKh4mIioiLCIKLi/tLBQ6L90sVi/dLBYuVj5OSk5KSk46WiwjdiwWPi5iPoZOkk6CRnZCdj56Nn4sIq4sFpougg5x8m3yTd4txCIuJBZd8kHuLd4uHi4eLh5J+jn6LfIuEi4SJhZR9kHyLeot3hHp8fH19eoR3iwhYiwWVeI95i3mLdIh6hH6EfoKBfoV+hX2He4uBi4OPg5KFkYaTh5SHlYiTipOKk4qTiJMIiZSIkYiPgZSBl4CaeKR+moSPCD2LBYCLg4+EkoSSh5SLlQiw9zgVi4aMh4+Ij4ePiZCLkIuPjY+Pjo6Nj4uQi5CJkIiOh4+HjYaLhouHiYeHh4iKhouGCNT7OBWUiwWOi46Kj4mPio+IjoiPh4+IjoePiI+Hj4aPho6HjoiNiI6Hj4aOho6Ii4qWfpKDj4YIk4ORgY5+j36OgI1/jYCPg5CGnYuXj5GUkpSOmYuei5aGmoKfgp6GmouWCPCLBZSLlI+SkpOTjpOLlYuSiZKHlIeUho+Fi46PjY+NkY2RjJCLkIuYhpaBlY6RjZKLkgiLkomSiJKIkoaQhY6MkIyRi5CLm4aXgpOBkn6Pe4sIZosFcotrhGN9iouIioaJh4qHiomKiYqIioaKh4mHioiKiYuHioiLh4qIi4mLCIKLi/tLBQ77lIv3txWLkpCPlo0I9yOgzPcWBY6SkI+RiwiL/BL7FUcFh4mHioiLh4uIjImOiY6KjouPi4yLjYyOCKP3IyPwBYaQiZCLjwgOi/fFFYu1l6yjoqOjrZe2i5aLl4mYh5eHloWWhJaElIWShZOEkoWShJKSkpGTkpKRlJGWkgiWkpaRl4+Yj5eNlou2i61/o3OjdJdqi2GLYXVgYGAI+0b7QAWHiIeJhouGi4eNh44I+0b3QAWJjYmNh4+IjoaRg5SElIWVhZSFlYaWh5mGmImYi5gIsIsVi2ucaa9oCPc6+zT3OvczBa+vnK2Lq4ubiZiHl4eXhpSFkoSSg5GCj4KQgo2CjYONgYuBi4KLgIl/hoCGgIWChAiBg4OFhISEhYaFhoaIhoaJhYuFi4aNiJCGkIaRhJGEkoORgZOCkoCRgJB/kICNgosIgYuBi4OJgomCiYKGgoeDhYSEhYSGgod/h3+Jfot7CA77JouyFYv4BAWLkY2Rj5GOkJCPko2PjY+Mj4sI98CLBY+Lj4qPiZGJkIePho+FjYWLhQiL/AQFi4SJhYeGh4aGh4WIiIqGioeLgYuEjoSRCPsS9w37EvsNBYSFg4iCi4eLh4uHjYSOho+IkIeQiZGLkgiwkxX3JvchpHL3DfsIi/f3+7iLi/v3BQ5ni8sVi/c5BYuSjpKQkJCQko6Siwj3VIuLwgWLrpippKSkpKmYrouvi6l+pHKkcpdti2gIi0IFi4aKhoeIh4eHiYaLCHmLBYaLh42Hj4eOipCLkAiL1AWLn4OcfZp9mXqSdot3i3qEfX18fIR6i3cIi1SniwWSi5KIkIaQho6Ei4QIi/s5BYuDiIWGhoaFhImEiwj7p4sFhIuEjYaRhpCIkYuTCA5njPe6FYyQkI6UjQj3I6DM9xYFj5KPj5GLkIuQh4+ECMv7FvcjdgWUiZCIjYaNhoiFhYUIIyak+yMFjIWKhomHiYiIiYaLiIuHjIeNCPsUz/sVRwWHiYeKiIuHi4eNiY6Jj4uQjJEIo/cjI/AFhZGJkY2QCPeB+z0VnILlW3rxiJ6ZmNTS+wydgpxe54v7pwUOZ4vCFYv3SwWLkI2Pjo+Pjo+NkIsI3osFkIuPiY6Ij4eNh4uGCIv7SwWLhomHh4eIh4eKhosIOIsFhouHjIePiI+Jj4uQCLCvFYuGjIePh46IkImQi5CLj42Pjo6PjY+LkIuQiZCIjoePh42Gi4aLhomIh4eIioaLhgjvZxWL90sFi5CNj46Oj4+PjZCLj4ySkJWWlZaVl5SXmJuVl5GRjo6OkI6RjZCNkIyPjI6MkY2TCIySjJGMj4yPjZCOkY6RjpCPjo6Pj42Qi5SLk4qSiZKJkYiPiJCIjoiPho6GjYeMhwiNh4yGjIaMhYuHi4iLiIuHi4eLg4uEiYSJhImFiYeJh4mFh4WLioqJiomJiIqJiokIi4qKiIqJCNqLBZqLmIWWgJaAkH+LfIt6hn2Af46DjYSLhIt9h36Cf4+Bi3+HgImAhYKEhI12hnmAfgh/fXiDcosIZosFfot+jHyOfI5/joOOg41/j32Qc5N8j4SMhouHjYiOh4+Jj4uQCA5ni/c5FYuGjYaOiI+Hj4mQiwjeiwWQi4+Njo+Pjo2Qi5AIi/dKBYuQiZCHjoiPh42Giwg4iwWGi4eJh4eIiImGi4YIi/tKBbD3JhWLkIyPj4+OjpCNkIuQi4+Jj4iOh42Hi4aLhomHiIeHh4eKhouGi4aMiI+Hj4qPi5AI7/snFYv3SwWLkI2Qj46Oj4+NkIuSi5qPo5OZkJePk46TjZeOmo6ajpiMmIsIsIsFpIueg5d9ln6Qeol1koSRgo2Aj4CLgIeAlH+Pfot9i4WJhIiCloCQfIt7i3yFfoGACICAfoZ8iwg8iwWMiIyJi4mMiYyJjYmMiIyKi4mPhI2GjYeNh42GjYOMhIyEi4SLhouHi4iLiYuGioYIioWKhomHioeJh4iGh4eIh4aIh4iFiISJhImDioKLhouHjYiPh4+Ij4iRiJGJkIqPCIqPipGKkomTipGKj4qOiZCJkYiQiJCIjoWSgZZ+nIKXgZaBloGWhJGHi4aLh42HjwiIjomQi48IDviUFPiUFYsMCgAAAAADAgABkAAFAAABTAFmAAAARwFMAWYAAAD1ABkAhAAAAAAAAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAAEAAAPFlAeD/4P/gAeAAIAAAAAEAAAAAAAAAAAAAACAAAAAAAAIAAAADAAAAFAADAAEAAAAUAAQAkAAAACAAIAAEAAAAAQAg5gXwBvAN8CPwLvBu8HDwivCX8JzxI/Fl//3//wAAAAAAIOYA8ATwDPAj8C7wbvBw8Ifwl/Cc8SPxZP/9//8AAf/jGgQQBhABD+wP4g+jD6IPjA+AD3wO9g62AAMAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAEAAJrVlLJfDzz1AAsCAAAAAADP/GODAAAAAM/8Y4MAAP/bAgAB2wAAAAgAAgAAAAAAAAABAAAB4P/gAAACAAAAAAACAAABAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAEAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAdwAAAHcAAACAAAjAZMAHwFJAAABbgAAAgAAAAIAAAACAAAAAgAAAAEAAAACAAAAAW4AAAHcAAAB3AABAdwAAAHcAAAAAFAAABwAAAAAAA4ArgABAAAAAAABAAwAAAABAAAAAAACAA4AQAABAAAAAAADAAwAIgABAAAAAAAEAAwATgABAAAAAAAFABYADAABAAAAAAAGAAYALgABAAAAAAAKADQAWgADAAEECQABAAwAAAADAAEECQACAA4AQAADAAEECQADAAwAIgADAAEECQAEAAwATgADAAEECQAFABYADAADAAEECQAGAAwANAADAAEECQAKADQAWgByAGEAdABpAG4AZwBWAGUAcgBzAGkAbwBuACAAMQAuADAAcgBhAHQAaQBuAGdyYXRpbmcAcgBhAHQAaQBuAGcAUgBlAGcAdQBsAGEAcgByAGEAdABpAG4AZwBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format('woff');font-weight:400;font-style:normal}.ui.rating .active.icon:before,.ui.rating .icon:before,.ui.star.rating .active.icon:before,.ui.star.rating .icon:before{content:'\f005'}.ui.star.rating .partial.icon:before{content:'\f006'}.ui.star.rating .partial.icon{content:'\f005'}.ui.heart.rating .active.icon:before,.ui.heart.rating .icon:before{content:'\f004'}.ui.search{position:relative}.ui.search>.prompt{margin:0;outline:0;-webkit-appearance:none;-webkit-tap-highlight-color:rgba(255,255,255,0);text-shadow:none;font-style:normal;font-weight:400;line-height:1.2142em;padding:.67861429em 1em;font-size:1em;background:#FFF;border:1px solid rgba(34,36,38,.15);color:rgba(0,0,0,.87);box-shadow:0 0 0 0 transparent inset;-webkit-transition:background-color .1s ease,color .1s ease,box-shadow .1s ease,border-color .1s ease;transition:background-color .1s ease,color .1s ease,box-shadow .1s ease,border-color .1s ease}.ui.search .prompt{border-radius:500rem}.ui.search .prompt~.search.icon{cursor:pointer}.ui.search>.results{display:none;position:absolute;top:100%;left:0;-webkit-transform-origin:center top;transform-origin:center top;white-space:normal;background:#FFF;margin-top:.5em;width:18em;border-radius:.28571429rem;box-shadow:0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.15);border:1px solid #D4D4D5;z-index:998}.ui.search>.results>:first-child{border-radius:.28571429rem .28571429rem 0 0}.ui.search>.results>:last-child{border-radius:0 0 .28571429rem .28571429rem}.ui.search>.results .result{cursor:pointer;display:block;overflow:hidden;font-size:1em;padding:.85714286em 1.14285714em;color:rgba(0,0,0,.87);line-height:1.33;border-bottom:1px solid rgba(34,36,38,.1)}.ui.search>.results .result:last-child{border-bottom:none!important}.ui.search>.results .result .image{float:right;overflow:hidden;background:0 0;width:5em;height:3em;border-radius:.25em}.ui.search>.results .result .image img{display:block;width:auto;height:100%}.ui.search>.results .result .image+.content{margin:0 6em 0 0}.ui.search>.results .result .title{margin:-.14285em 0 0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:700;font-size:1em;color:rgba(0,0,0,.85)}.ui.search>.results .result .description{margin-top:0;font-size:.92857143em;color:rgba(0,0,0,.4)}.ui.search>.results .result .price{float:right;color:#21BA45}.ui.search>.results>.message{padding:1em}.ui.search>.results>.message .header{font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:1rem;font-weight:700;color:rgba(0,0,0,.87)}.ui.search>.results>.message .description{margin-top:.25rem;font-size:1em;color:rgba(0,0,0,.87)}.ui.search>.results>.action{display:block;border-top:none;background:#F3F4F5;padding:.92857143em 1em;color:rgba(0,0,0,.87);font-weight:700;text-align:center}.ui.search>.prompt:focus{border-color:rgba(34,36,38,.35);background:#FFF;color:rgba(0,0,0,.95)}.ui.loading.search .input>i.icon:before{position:absolute;content:'';top:50%;left:50%;margin:-.64285714em 0 0 -.64285714em;width:1.28571429em;height:1.28571429em;border-radius:500rem;border:.2em solid rgba(0,0,0,.1)}.ui.loading.search .input>i.icon:after{position:absolute;content:'';top:50%;left:50%;margin:-.64285714em 0 0 -.64285714em;width:1.28571429em;height:1.28571429em;-webkit-animation:button-spin .6s linear;animation:button-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 transparent transparent;border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent}.ui.category.search>.results .category .result:hover,.ui.search>.results .result:hover{background:#F9FAFB}.ui.search .action:hover{background:#E0E0E0}.ui.category.search>.results .category.active{background:#F3F4F5}.ui.category.search>.results .category.active>.name{color:rgba(0,0,0,.87)}.ui.category.search>.results .category .result.active,.ui.search>.results .result.active{position:relative;border-left-color:rgba(34,36,38,.1);background:#F3F4F5;box-shadow:none}.ui.search>.results .result.active .description,.ui.search>.results .result.active .title{color:rgba(0,0,0,.85)}.ui.search.selection .prompt{border-radius:.28571429rem}.ui.search.selection>.icon.input>.remove.icon{pointer-events:none;position:absolute;left:auto;opacity:0;color:'';top:0;right:0;-webkit-transition:color .1s ease,opacity .1s ease;transition:color .1s ease,opacity .1s ease}.ui.search.selection>.icon.input>.active.remove.icon{cursor:pointer;opacity:.8;pointer-events:auto}.ui.search.selection>.icon.input:not([class*="left icon"])>.icon~.remove.icon{right:1.85714em}.ui.search.selection>.icon.input>.remove.icon:hover{opacity:1;color:#DB2828}.ui.category.search .results{width:28em}.ui.category.search>.results .category{background:#F3F4F5;box-shadow:none;border-bottom:1px solid rgba(34,36,38,.1);-webkit-transition:background .1s ease,border-color .1s ease;transition:background .1s ease,border-color .1s ease}.ui.category.search>.results .category:last-child{border-bottom:none}.ui.category.search>.results .category:first-child .name+.result{border-radius:0 .28571429rem 0 0}.ui.category.search>.results .category .result{background:#FFF;margin-left:100px;border-left:1px solid rgba(34,36,38,.15);border-bottom:1px solid rgba(34,36,38,.1);-webkit-transition:background .1s ease,border-color .1s ease;transition:background .1s ease,border-color .1s ease;padding:.85714286em 1.14285714em}.ui.category.search>.results .category:last-child .result:last-child{border-radius:0 0 .28571429rem;border-bottom:none}.ui.category.search>.results .category>.name{width:100px;background:0 0;font-family:Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;font-size:1em;float:1em;float:left;padding:.4em 1em;font-weight:700;color:rgba(0,0,0,.4)}.ui[class*="left aligned"].search>.results{right:auto;left:0}.ui[class*="right aligned"].search>.results{right:0;left:auto}.ui.fluid.search .results{width:100%}.ui.mini.search{font-size:.78571429em}.ui.small.search{font-size:.92857143em}.ui.search{font-size:1em}.ui.large.search{font-size:1.14285714em}.ui.big.search{font-size:1.28571429em}.ui.huge.search{font-size:1.42857143em}.ui.massive.search{font-size:1.71428571em}.ui.shape{position:relative;vertical-align:top;display:inline-block;-webkit-perspective:2000px;perspective:2000px;-webkit-transition:left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out,-webkit-transform .6s ease-in-out;transition:left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out,-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out,left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out;transition:transform .6s ease-in-out,left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out,-webkit-transform .6s ease-in-out}.ui.shape .sides{-webkit-transform-style:preserve-3d;transform-style:preserve-3d}.ui.shape .side{opacity:1;width:100%;margin:0!important;-webkit-backface-visibility:hidden;backface-visibility:hidden;display:none}.ui.shape .side *{-webkit-backface-visibility:visible!important;backface-visibility:visible!important}.ui.cube.shape .side{min-width:15em;height:15em;padding:2em;background-color:#E6E6E6;color:rgba(0,0,0,.87);box-shadow:0 0 2px rgba(0,0,0,.3)}.ui.cube.shape .side>.content{width:100%;height:100%;display:table;text-align:center;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ui.cube.shape .side>.content>div{display:table-cell;vertical-align:middle;font-size:2em}.ui.text.shape.animating .sides{position:static}.ui.text.shape .side{white-space:nowrap}.ui.text.shape .side>*{white-space:normal}.ui.loading.shape{position:absolute;top:-9999px;left:-9999px}.ui.shape .animating.side{position:absolute;top:0;left:0;display:block;z-index:100}.ui.shape .hidden.side{opacity:.6}.ui.shape.animating .sides{position:absolute;-webkit-transition:left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out,-webkit-transform .6s ease-in-out;transition:left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out,-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out,left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out;transition:transform .6s ease-in-out,left .6s ease-in-out,width .6s ease-in-out,height .6s ease-in-out,-webkit-transform .6s ease-in-out}.ui.shape.animating .side{-webkit-transition:opacity .6s ease-in-out;transition:opacity .6s ease-in-out}.ui.shape .active.side{display:block}.ui.sidebar{position:fixed;top:0;left:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition:none;transition:none;will-change:transform;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);visibility:hidden;-webkit-overflow-scrolling:touch;height:100%!important;max-height:100%;border-radius:0!important;margin:0!important;overflow-y:auto!important;z-index:102}.ui.sidebar>*{-webkit-backface-visibility:hidden;backface-visibility:hidden}.ui.left.sidebar{right:auto;left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.ui.right.sidebar{right:0!important;left:auto!important;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.ui.bottom.sidebar,.ui.top.sidebar{width:100%!important;height:auto!important}.ui.top.sidebar{top:0!important;bottom:auto!important;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}.ui.bottom.sidebar{top:auto!important;bottom:0!important;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}.pushable{height:100%;overflow-x:hidden;padding:0!important}body.pushable{background:#545454!important}.pushable:not(body){-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.pushable:not(body)>.fixed,.pushable:not(body)>.pusher:after,.pushable:not(body)>.ui.sidebar{position:absolute}.pushable>.fixed{position:fixed;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;will-change:transform;z-index:101}body.pushable>.pusher{background:#FFF}.pushable>.pusher{position:relative;-webkit-backface-visibility:hidden;backface-visibility:hidden;overflow:hidden;min-height:100%;-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;z-index:2;background:inherit}.pushable>.pusher:after{position:fixed;top:0;right:0;content:'';background-color:rgba(0,0,0,.4);overflow:hidden;opacity:0;-webkit-transition:opacity .5s;transition:opacity .5s;will-change:opacity;z-index:1000}.ui.sidebar.menu .item{border-radius:0!important}.pushable>.pusher.dimmed:after{width:100%!important;height:100%!important;opacity:1!important}.ui.animating.sidebar{visibility:visible}.ui.visible.sidebar{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.bottom.visible.sidebar,.ui.left.visible.sidebar,.ui.right.visible.sidebar,.ui.top.visible.sidebar{box-shadow:0 0 20px rgba(34,36,38,.15)}.ui.visible.left.sidebar~.fixed,.ui.visible.left.sidebar~.pusher{-webkit-transform:translate3d(260px,0,0);transform:translate3d(260px,0,0)}.ui.visible.right.sidebar~.fixed,.ui.visible.right.sidebar~.pusher{-webkit-transform:translate3d(-260px,0,0);transform:translate3d(-260px,0,0)}.ui.visible.top.sidebar~.fixed,.ui.visible.top.sidebar~.pusher{-webkit-transform:translate3d(0,36px,0);transform:translate3d(0,36px,0)}.ui.visible.bottom.sidebar~.fixed,.ui.visible.bottom.sidebar~.pusher{-webkit-transform:translate3d(0,-36px,0);transform:translate3d(0,-36px,0)}.ui.visible.left.sidebar~.ui.visible.right.sidebar~.fixed,.ui.visible.left.sidebar~.ui.visible.right.sidebar~.pusher,.ui.visible.right.sidebar~.ui.visible.left.sidebar~.fixed,.ui.visible.right.sidebar~.ui.visible.left.sidebar~.pusher{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}html.ios{overflow-x:hidden;-webkit-overflow-scrolling:touch}html.ios,html.ios body{height:initial!important}.ui.thin.left.sidebar,.ui.thin.right.sidebar{width:150px}.ui[class*="very thin"].left.sidebar,.ui[class*="very thin"].right.sidebar{width:60px}.ui.left.sidebar,.ui.right.sidebar{width:260px}.ui.wide.left.sidebar,.ui.wide.right.sidebar{width:350px}.ui[class*="very wide"].left.sidebar,.ui[class*="very wide"].right.sidebar{width:475px}.ui.visible.thin.left.sidebar~.fixed,.ui.visible.thin.left.sidebar~.pusher{-webkit-transform:translate3d(150px,0,0);transform:translate3d(150px,0,0)}.ui.visible[class*="very thin"].left.sidebar~.fixed,.ui.visible[class*="very thin"].left.sidebar~.pusher{-webkit-transform:translate3d(60px,0,0);transform:translate3d(60px,0,0)}.ui.visible.wide.left.sidebar~.fixed,.ui.visible.wide.left.sidebar~.pusher{-webkit-transform:translate3d(350px,0,0);transform:translate3d(350px,0,0)}.ui.visible[class*="very wide"].left.sidebar~.fixed,.ui.visible[class*="very wide"].left.sidebar~.pusher{-webkit-transform:translate3d(475px,0,0);transform:translate3d(475px,0,0)}.ui.visible.thin.right.sidebar~.fixed,.ui.visible.thin.right.sidebar~.pusher{-webkit-transform:translate3d(-150px,0,0);transform:translate3d(-150px,0,0)}.ui.visible[class*="very thin"].right.sidebar~.fixed,.ui.visible[class*="very thin"].right.sidebar~.pusher{-webkit-transform:translate3d(-60px,0,0);transform:translate3d(-60px,0,0)}.ui.visible.wide.right.sidebar~.fixed,.ui.visible.wide.right.sidebar~.pusher{-webkit-transform:translate3d(-350px,0,0);transform:translate3d(-350px,0,0)}.ui.visible[class*="very wide"].right.sidebar~.fixed,.ui.visible[class*="very wide"].right.sidebar~.pusher{-webkit-transform:translate3d(-475px,0,0);transform:translate3d(-475px,0,0)}.ui.overlay.sidebar{z-index:102}.ui.left.overlay.sidebar{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.ui.right.overlay.sidebar{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.ui.top.overlay.sidebar{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}.ui.bottom.overlay.sidebar{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}.animating.ui.overlay.sidebar,.ui.visible.overlay.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.visible.bottom.overlay.sidebar,.ui.visible.left.overlay.sidebar,.ui.visible.right.overlay.sidebar,.ui.visible.top.overlay.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.visible.overlay.sidebar~.fixed,.ui.visible.overlay.sidebar~.pusher{-webkit-transform:none!important;transform:none!important}.ui.push.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;z-index:102}.ui.left.push.sidebar{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.ui.right.push.sidebar{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.ui.top.push.sidebar{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}.ui.bottom.push.sidebar{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}.ui.visible.push.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.uncover.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);z-index:1}.ui.visible.uncover.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.slide.along.sidebar{z-index:1}.ui.left.slide.along.sidebar{-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0)}.ui.right.slide.along.sidebar{-webkit-transform:translate3d(50%,0,0);transform:translate3d(50%,0,0)}.ui.top.slide.along.sidebar{-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}.ui.bottom.slide.along.sidebar{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0)}.ui.animating.slide.along.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.visible.slide.along.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.slide.out.sidebar{z-index:1}.ui.left.slide.out.sidebar{-webkit-transform:translate3d(50%,0,0);transform:translate3d(50%,0,0)}.ui.right.slide.out.sidebar{-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0)}.ui.top.slide.out.sidebar{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0)}.ui.bottom.slide.out.sidebar{-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}.ui.animating.slide.out.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.visible.slide.out.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.scale.down.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease;z-index:102}.ui.left.scale.down.sidebar{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.ui.right.scale.down.sidebar{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.ui.top.scale.down.sidebar{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}.ui.bottom.scale.down.sidebar{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}.ui.scale.down.left.sidebar~.pusher{-webkit-transform-origin:75% 50%;transform-origin:75% 50%}.ui.scale.down.right.sidebar~.pusher{-webkit-transform-origin:25% 50%;transform-origin:25% 50%}.ui.scale.down.top.sidebar~.pusher{-webkit-transform-origin:50% 75%;transform-origin:50% 75%}.ui.scale.down.bottom.sidebar~.pusher{-webkit-transform-origin:50% 25%;transform-origin:50% 25%}.ui.animating.scale.down>.visible.ui.sidebar{-webkit-transition:-webkit-transform .5s ease;transition:-webkit-transform .5s ease;transition:transform .5s ease;transition:transform .5s ease,-webkit-transform .5s ease}.ui.animating.scale.down.sidebar~.pusher,.ui.visible.scale.down.sidebar~.pusher{display:block!important;width:100%;height:100%;overflow:hidden!important}.ui.visible.scale.down.sidebar{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.ui.visible.scale.down.sidebar~.pusher{-webkit-transform:scale(.75);transform:scale(.75)}.ui.sticky{position:static;-webkit-transition:none;transition:none;z-index:800}.ui.sticky.bound{position:absolute;left:auto;right:auto}.ui.sticky.fixed{position:fixed;left:auto;right:auto}.ui.sticky.bound.top,.ui.sticky.fixed.top{top:0;bottom:auto}.ui.sticky.bound.bottom,.ui.sticky.fixed.bottom{top:auto;bottom:0}.ui.native.sticky{position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;position:sticky}.ui.tab{display:none}.ui.tab.active,.ui.tab.open{display:block}.ui.tab.loading{position:relative;overflow:hidden;display:block;min-height:250px}.ui.tab.loading *{position:relative!important;left:-10000px!important}.ui.tab.loading.segment:before,.ui.tab.loading:before{position:absolute;content:'';top:100px;left:50%;margin:-1.25em 0 0 -1.25em;width:2.5em;height:2.5em;border-radius:500rem;border:.2em solid rgba(0,0,0,.1)}.ui.tab.loading.segment:after,.ui.tab.loading:after{position:absolute;content:'';top:100px;left:50%;margin:-1.25em 0 0 -1.25em;width:2.5em;height:2.5em;-webkit-animation:button-spin .6s linear;animation:button-spin .6s linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;border-radius:500rem;border-color:#767676 transparent transparent;border-style:solid;border-width:.2em;box-shadow:0 0 0 1px transparent}.transition{-webkit-animation-iteration-count:1;animation-iteration-count:1;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-timing-function:ease;animation-timing-function:ease;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animating.transition{-webkit-backface-visibility:hidden;backface-visibility:hidden;visibility:visible!important}.loading.transition{position:absolute;top:-99999px;left:-99999px}.hidden.transition{display:none;visibility:hidden}.visible.transition{display:block!important;visibility:visible!important}.disabled.transition{-webkit-animation-play-state:paused;animation-play-state:paused}.looping.transition{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.transition.browse{-webkit-animation-duration:.5s;animation-duration:.5s}.transition.browse.in{-webkit-animation-name:browseIn;animation-name:browseIn}.transition.browse.left.out,.transition.browse.out{-webkit-animation-name:browseOutLeft;animation-name:browseOutLeft}.transition.browse.right.out{-webkit-animation-name:browseOutRight;animation-name:browseOutRight}@-webkit-keyframes browseIn{0%{-webkit-transform:scale(.8) translateZ(0);transform:scale(.8) translateZ(0);z-index:-1}10%{-webkit-transform:scale(.8) translateZ(0);transform:scale(.8) translateZ(0);z-index:-1;opacity:.7}80%{-webkit-transform:scale(1.05) translateZ(0);transform:scale(1.05) translateZ(0);opacity:1;z-index:999}100%{-webkit-transform:scale(1) translateZ(0);transform:scale(1) translateZ(0);z-index:999}}@keyframes browseIn{0%{-webkit-transform:scale(.8) translateZ(0);transform:scale(.8) translateZ(0);z-index:-1}10%{-webkit-transform:scale(.8) translateZ(0);transform:scale(.8) translateZ(0);z-index:-1;opacity:.7}80%{-webkit-transform:scale(1.05) translateZ(0);transform:scale(1.05) translateZ(0);opacity:1;z-index:999}100%{-webkit-transform:scale(1) translateZ(0);transform:scale(1) translateZ(0);z-index:999}}@-webkit-keyframes browseOutLeft{0%{z-index:999;-webkit-transform:translateX(0) rotateY(0) rotateX(0);transform:translateX(0) rotateY(0) rotateX(0)}50%{z-index:-1;-webkit-transform:translateX(-105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);transform:translateX(-105%) rotateY(35deg) rotateX(10deg) translateZ(-10px)}80%{opacity:1}100%{z-index:-1;-webkit-transform:translateX(0) rotateY(0) rotateX(0) translateZ(-10px);transform:translateX(0) rotateY(0) rotateX(0) translateZ(-10px);opacity:0}}@keyframes browseOutLeft{0%{z-index:999;-webkit-transform:translateX(0) rotateY(0) rotateX(0);transform:translateX(0) rotateY(0) rotateX(0)}50%{z-index:-1;-webkit-transform:translateX(-105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);transform:translateX(-105%) rotateY(35deg) rotateX(10deg) translateZ(-10px)}80%{opacity:1}100%{z-index:-1;-webkit-transform:translateX(0) rotateY(0) rotateX(0) translateZ(-10px);transform:translateX(0) rotateY(0) rotateX(0) translateZ(-10px);opacity:0}}@-webkit-keyframes browseOutRight{0%{z-index:999;-webkit-transform:translateX(0) rotateY(0) rotateX(0);transform:translateX(0) rotateY(0) rotateX(0)}50%{z-index:1;-webkit-transform:translateX(105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);transform:translateX(105%) rotateY(35deg) rotateX(10deg) translateZ(-10px)}80%{opacity:1}100%{z-index:1;-webkit-transform:translateX(0) rotateY(0) rotateX(0) translateZ(-10px);transform:translateX(0) rotateY(0) rotateX(0) translateZ(-10px);opacity:0}}@keyframes browseOutRight{0%{z-index:999;-webkit-transform:translateX(0) rotateY(0) rotateX(0);transform:translateX(0) rotateY(0) rotateX(0)}50%{z-index:1;-webkit-transform:translateX(105%) rotateY(35deg) rotateX(10deg) translateZ(-10px);transform:translateX(105%) rotateY(35deg) rotateX(10deg) translateZ(-10px)}80%{opacity:1}100%{z-index:1;-webkit-transform:translateX(0) rotateY(0) rotateX(0) translateZ(-10px);transform:translateX(0) rotateY(0) rotateX(0) translateZ(-10px);opacity:0}}.drop.transition{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-duration:.4s;animation-duration:.4s;-webkit-animation-timing-function:cubic-bezier(.34,1.61,.7,1);animation-timing-function:cubic-bezier(.34,1.61,.7,1)}.drop.transition.in{-webkit-animation-name:dropIn;animation-name:dropIn}.drop.transition.out{-webkit-animation-name:dropOut;animation-name:dropOut}@-webkit-keyframes dropIn{0%{opacity:0;-webkit-transform:scale(0);transform:scale(0)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes dropIn{0%{opacity:0;-webkit-transform:scale(0);transform:scale(0)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes dropOut{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(0);transform:scale(0)}}@keyframes dropOut{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(0);transform:scale(0)}}.transition.fade.in{-webkit-animation-name:fadeIn;animation-name:fadeIn}.transition[class*="fade up"].in{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}.transition[class*="fade down"].in{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}.transition[class*="fade left"].in{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}.transition[class*="fade right"].in{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}.transition.fade.out{-webkit-animation-name:fadeOut;animation-name:fadeOut}.transition[class*="fade up"].out{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}.transition[class*="fade down"].out{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}.transition[class*="fade left"].out{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}.transition[class*="fade right"].out{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(10%);transform:translateY(10%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(10%);transform:translateY(10%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-10%);transform:translateY(-10%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-10%);transform:translateY(-10%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translateX(10%);transform:translateX(10%)}100%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translateX(10%);transform:translateX(10%)}100%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translateX(-10%);transform:translateX(-10%)}100%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translateX(-10%);transform:translateX(-10%)}100%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-webkit-keyframes fadeOutUp{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(5%);transform:translateY(5%)}}@keyframes fadeOutUp{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(5%);transform:translateY(5%)}}@-webkit-keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-5%);transform:translateY(-5%)}}@keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-5%);transform:translateY(-5%)}}@-webkit-keyframes fadeOutLeft{0%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(5%);transform:translateX(5%)}}@keyframes fadeOutLeft{0%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(5%);transform:translateX(5%)}}@-webkit-keyframes fadeOutRight{0%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(-5%);transform:translateX(-5%)}}@keyframes fadeOutRight{0%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}100%{opacity:0;-webkit-transform:translateX(-5%);transform:translateX(-5%)}}.flip.transition.in,.flip.transition.out{-webkit-animation-duration:.6s;animation-duration:.6s}.horizontal.flip.transition.in{-webkit-animation-name:horizontalFlipIn;animation-name:horizontalFlipIn}.horizontal.flip.transition.out{-webkit-animation-name:horizontalFlipOut;animation-name:horizontalFlipOut}.vertical.flip.transition.in{-webkit-animation-name:verticalFlipIn;animation-name:verticalFlipIn}.vertical.flip.transition.out{-webkit-animation-name:verticalFlipOut;animation-name:verticalFlipOut}@-webkit-keyframes horizontalFlipIn{0%{-webkit-transform:perspective(2000px) rotateY(-90deg);transform:perspective(2000px) rotateY(-90deg);opacity:0}100%{-webkit-transform:perspective(2000px) rotateY(0);transform:perspective(2000px) rotateY(0);opacity:1}}@keyframes horizontalFlipIn{0%{-webkit-transform:perspective(2000px) rotateY(-90deg);transform:perspective(2000px) rotateY(-90deg);opacity:0}100%{-webkit-transform:perspective(2000px) rotateY(0);transform:perspective(2000px) rotateY(0);opacity:1}}@-webkit-keyframes verticalFlipIn{0%{-webkit-transform:perspective(2000px) rotateX(-90deg);transform:perspective(2000px) rotateX(-90deg);opacity:0}100%{-webkit-transform:perspective(2000px) rotateX(0);transform:perspective(2000px) rotateX(0);opacity:1}}@keyframes verticalFlipIn{0%{-webkit-transform:perspective(2000px) rotateX(-90deg);transform:perspective(2000px) rotateX(-90deg);opacity:0}100%{-webkit-transform:perspective(2000px) rotateX(0);transform:perspective(2000px) rotateX(0);opacity:1}}@-webkit-keyframes horizontalFlipOut{0%{-webkit-transform:perspective(2000px) rotateY(0);transform:perspective(2000px) rotateY(0);opacity:1}100%{-webkit-transform:perspective(2000px) rotateY(90deg);transform:perspective(2000px) rotateY(90deg);opacity:0}}@keyframes horizontalFlipOut{0%{-webkit-transform:perspective(2000px) rotateY(0);transform:perspective(2000px) rotateY(0);opacity:1}100%{-webkit-transform:perspective(2000px) rotateY(90deg);transform:perspective(2000px) rotateY(90deg);opacity:0}}@-webkit-keyframes verticalFlipOut{0%{-webkit-transform:perspective(2000px) rotateX(0);transform:perspective(2000px) rotateX(0);opacity:1}100%{-webkit-transform:perspective(2000px) rotateX(-90deg);transform:perspective(2000px) rotateX(-90deg);opacity:0}}@keyframes verticalFlipOut{0%{-webkit-transform:perspective(2000px) rotateX(0);transform:perspective(2000px) rotateX(0);opacity:1}100%{-webkit-transform:perspective(2000px) rotateX(-90deg);transform:perspective(2000px) rotateX(-90deg);opacity:0}}.scale.transition.in{-webkit-animation-name:scaleIn;animation-name:scaleIn}.scale.transition.out{-webkit-animation-name:scaleOut;animation-name:scaleOut}@-webkit-keyframes scaleIn{0%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes scaleIn{0%{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes scaleOut{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.9);transform:scale(.9)}}@keyframes scaleOut{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.9);transform:scale(.9)}}.transition.fly{-webkit-animation-duration:.6s;animation-duration:.6s;-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1);transition-timing-function:cubic-bezier(.215,.61,.355,1)}.transition.fly.in{-webkit-animation-name:flyIn;animation-name:flyIn}.transition[class*="fly up"].in{-webkit-animation-name:flyInUp;animation-name:flyInUp}.transition[class*="fly down"].in{-webkit-animation-name:flyInDown;animation-name:flyInDown}.transition[class*="fly left"].in{-webkit-animation-name:flyInLeft;animation-name:flyInLeft}.transition[class*="fly right"].in{-webkit-animation-name:flyInRight;animation-name:flyInRight}.transition.fly.out{-webkit-animation-name:flyOut;animation-name:flyOut}.transition[class*="fly up"].out{-webkit-animation-name:flyOutUp;animation-name:flyOutUp}.transition[class*="fly down"].out{-webkit-animation-name:flyOutDown;animation-name:flyOutDown}.transition[class*="fly left"].out{-webkit-animation-name:flyOutLeft;animation-name:flyOutLeft}.transition[class*="fly right"].out{-webkit-animation-name:flyOutRight;animation-name:flyOutRight}@-webkit-keyframes flyIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@keyframes flyIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}100%{opacity:1;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}}@-webkit-keyframes flyInUp{0%{opacity:0;-webkit-transform:translate3d(0,1500px,0);transform:translate3d(0,1500px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes flyInUp{0%{opacity:0;-webkit-transform:translate3d(0,1500px,0);transform:translate3d(0,1500px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@-webkit-keyframes flyInDown{0%{opacity:0;-webkit-transform:translate3d(0,-1500px,0);transform:translate3d(0,-1500px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}100%{-webkit-transform:none;transform:none}}@keyframes flyInDown{0%{opacity:0;-webkit-transform:translate3d(0,-1500px,0);transform:translate3d(0,-1500px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}100%{-webkit-transform:none;transform:none}}@-webkit-keyframes flyInLeft{0%{opacity:0;-webkit-transform:translate3d(1500px,0,0);transform:translate3d(1500px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}100%{-webkit-transform:none;transform:none}}@keyframes flyInLeft{0%{opacity:0;-webkit-transform:translate3d(1500px,0,0);transform:translate3d(1500px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}100%{-webkit-transform:none;transform:none}}@-webkit-keyframes flyInRight{0%{opacity:0;-webkit-transform:translate3d(-1500px,0,0);transform:translate3d(-1500px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}100%{-webkit-transform:none;transform:none}}@keyframes flyInRight{0%{opacity:0;-webkit-transform:translate3d(-1500px,0,0);transform:translate3d(-1500px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}100%{-webkit-transform:none;transform:none}}@-webkit-keyframes flyOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes flyOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}100%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@-webkit-keyframes flyOutUp{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes flyOutUp{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@-webkit-keyframes flyOutDown{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes flyOutDown{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}100%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@-webkit-keyframes flyOutRight{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes flyOutRight{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@-webkit-keyframes flyOutLeft{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes flyOutLeft{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}100%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.transition.slide.in,.transition[class*="slide down"].in{-webkit-animation-name:slideInY;animation-name:slideInY;-webkit-transform-origin:top center;transform-origin:top center}.transition[class*="slide up"].in{-webkit-animation-name:slideInY;animation-name:slideInY;-webkit-transform-origin:bottom center;transform-origin:bottom center}.transition[class*="slide left"].in{-webkit-animation-name:slideInX;animation-name:slideInX;-webkit-transform-origin:center right;transform-origin:center right}.transition[class*="slide right"].in{-webkit-animation-name:slideInX;animation-name:slideInX;-webkit-transform-origin:center left;transform-origin:center left}.transition.slide.out,.transition[class*="slide down"].out{-webkit-animation-name:slideOutY;animation-name:slideOutY;-webkit-transform-origin:top center;transform-origin:top center}.transition[class*="slide up"].out{-webkit-animation-name:slideOutY;animation-name:slideOutY;-webkit-transform-origin:bottom center;transform-origin:bottom center}.transition[class*="slide left"].out{-webkit-animation-name:slideOutX;animation-name:slideOutX;-webkit-transform-origin:center right;transform-origin:center right}.transition[class*="slide right"].out{-webkit-animation-name:slideOutX;animation-name:slideOutX;-webkit-transform-origin:center left;transform-origin:center left}@-webkit-keyframes slideInY{0%{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}100%{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}}@keyframes slideInY{0%{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}100%{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}}@-webkit-keyframes slideInX{0%{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}100%{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes slideInX{0%{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}100%{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}@-webkit-keyframes slideOutY{0%{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}100%{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}}@keyframes slideOutY{0%{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1)}100%{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}}@-webkit-keyframes slideOutX{0%{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}100%{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}}@keyframes slideOutX{0%{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}100%{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}}.transition.swing{-webkit-animation-duration:.8s;animation-duration:.8s}.transition[class*="swing down"].in{-webkit-animation-name:swingInX;animation-name:swingInX;-webkit-transform-origin:top center;transform-origin:top center}.transition[class*="swing up"].in{-webkit-animation-name:swingInX;animation-name:swingInX;-webkit-transform-origin:bottom center;transform-origin:bottom center}.transition[class*="swing left"].in{-webkit-animation-name:swingInY;animation-name:swingInY;-webkit-transform-origin:center right;transform-origin:center right}.transition[class*="swing right"].in{-webkit-animation-name:swingInY;animation-name:swingInY;-webkit-transform-origin:center left;transform-origin:center left}.transition.swing.out,.transition[class*="swing down"].out{-webkit-animation-name:swingOutX;animation-name:swingOutX;-webkit-transform-origin:top center;transform-origin:top center}.transition[class*="swing up"].out{-webkit-animation-name:swingOutX;animation-name:swingOutX;-webkit-transform-origin:bottom center;transform-origin:bottom center}.transition[class*="swing left"].out{-webkit-animation-name:swingOutY;animation-name:swingOutY;-webkit-transform-origin:center right;transform-origin:center right}.transition[class*="swing right"].out{-webkit-animation-name:swingOutY;animation-name:swingOutY;-webkit-transform-origin:center left;transform-origin:center left}@-webkit-keyframes swingInX{0%{-webkit-transform:perspective(1000px) rotateX(90deg);transform:perspective(1000px) rotateX(90deg);opacity:0}40%{-webkit-transform:perspective(1000px) rotateX(-30deg);transform:perspective(1000px) rotateX(-30deg);opacity:1}60%{-webkit-transform:perspective(1000px) rotateX(15deg);transform:perspective(1000px) rotateX(15deg)}80%{-webkit-transform:perspective(1000px) rotateX(-7.5deg);transform:perspective(1000px) rotateX(-7.5deg)}100%{-webkit-transform:perspective(1000px) rotateX(0);transform:perspective(1000px) rotateX(0)}}@keyframes swingInX{0%{-webkit-transform:perspective(1000px) rotateX(90deg);transform:perspective(1000px) rotateX(90deg);opacity:0}40%{-webkit-transform:perspective(1000px) rotateX(-30deg);transform:perspective(1000px) rotateX(-30deg);opacity:1}60%{-webkit-transform:perspective(1000px) rotateX(15deg);transform:perspective(1000px) rotateX(15deg)}80%{-webkit-transform:perspective(1000px) rotateX(-7.5deg);transform:perspective(1000px) rotateX(-7.5deg)}100%{-webkit-transform:perspective(1000px) rotateX(0);transform:perspective(1000px) rotateX(0)}}@-webkit-keyframes swingInY{0%{-webkit-transform:perspective(1000px) rotateY(-90deg);transform:perspective(1000px) rotateY(-90deg);opacity:0}40%{-webkit-transform:perspective(1000px) rotateY(30deg);transform:perspective(1000px) rotateY(30deg);opacity:1}60%{-webkit-transform:perspective(1000px) rotateY(-17.5deg);transform:perspective(1000px) rotateY(-17.5deg)}80%{-webkit-transform:perspective(1000px) rotateY(7.5deg);transform:perspective(1000px) rotateY(7.5deg)}100%{-webkit-transform:perspective(1000px) rotateY(0);transform:perspective(1000px) rotateY(0)}}@keyframes swingInY{0%{-webkit-transform:perspective(1000px) rotateY(-90deg);transform:perspective(1000px) rotateY(-90deg);opacity:0}40%{-webkit-transform:perspective(1000px) rotateY(30deg);transform:perspective(1000px) rotateY(30deg);opacity:1}60%{-webkit-transform:perspective(1000px) rotateY(-17.5deg);transform:perspective(1000px) rotateY(-17.5deg)}80%{-webkit-transform:perspective(1000px) rotateY(7.5deg);transform:perspective(1000px) rotateY(7.5deg)}100%{-webkit-transform:perspective(1000px) rotateY(0);transform:perspective(1000px) rotateY(0)}}@-webkit-keyframes swingOutX{0%{-webkit-transform:perspective(1000px) rotateX(0);transform:perspective(1000px) rotateX(0)}40%{-webkit-transform:perspective(1000px) rotateX(-7.5deg);transform:perspective(1000px) rotateX(-7.5deg)}60%{-webkit-transform:perspective(1000px) rotateX(17.5deg);transform:perspective(1000px) rotateX(17.5deg)}80%{-webkit-transform:perspective(1000px) rotateX(-30deg);transform:perspective(1000px) rotateX(-30deg);opacity:1}100%{-webkit-transform:perspective(1000px) rotateX(90deg);transform:perspective(1000px) rotateX(90deg);opacity:0}}@keyframes swingOutX{0%{-webkit-transform:perspective(1000px) rotateX(0);transform:perspective(1000px) rotateX(0)}40%{-webkit-transform:perspective(1000px) rotateX(-7.5deg);transform:perspective(1000px) rotateX(-7.5deg)}60%{-webkit-transform:perspective(1000px) rotateX(17.5deg);transform:perspective(1000px) rotateX(17.5deg)}80%{-webkit-transform:perspective(1000px) rotateX(-30deg);transform:perspective(1000px) rotateX(-30deg);opacity:1}100%{-webkit-transform:perspective(1000px) rotateX(90deg);transform:perspective(1000px) rotateX(90deg);opacity:0}}@-webkit-keyframes swingOutY{0%{-webkit-transform:perspective(1000px) rotateY(0);transform:perspective(1000px) rotateY(0)}40%{-webkit-transform:perspective(1000px) rotateY(7.5deg);transform:perspective(1000px) rotateY(7.5deg)}60%{-webkit-transform:perspective(1000px) rotateY(-10deg);transform:perspective(1000px) rotateY(-10deg)}80%{-webkit-transform:perspective(1000px) rotateY(30deg);transform:perspective(1000px) rotateY(30deg);opacity:1}100%{-webkit-transform:perspective(1000px) rotateY(-90deg);transform:perspective(1000px) rotateY(-90deg);opacity:0}}@keyframes swingOutY{0%{-webkit-transform:perspective(1000px) rotateY(0);transform:perspective(1000px) rotateY(0)}40%{-webkit-transform:perspective(1000px) rotateY(7.5deg);transform:perspective(1000px) rotateY(7.5deg)}60%{-webkit-transform:perspective(1000px) rotateY(-10deg);transform:perspective(1000px) rotateY(-10deg)}80%{-webkit-transform:perspective(1000px) rotateY(30deg);transform:perspective(1000px) rotateY(30deg);opacity:1}100%{-webkit-transform:perspective(1000px) rotateY(-90deg);transform:perspective(1000px) rotateY(-90deg);opacity:0}}.flash.transition{-webkit-animation-duration:750ms;animation-duration:750ms;-webkit-animation-name:flash;animation-name:flash}.shake.transition{-webkit-animation-duration:750ms;animation-duration:750ms;-webkit-animation-name:shake;animation-name:shake}.bounce.transition{-webkit-animation-duration:750ms;animation-duration:750ms;-webkit-animation-name:bounce;animation-name:bounce}.tada.transition{-webkit-animation-duration:750ms;animation-duration:750ms;-webkit-animation-name:tada;animation-name:tada}.pulse.transition{-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-name:pulse;animation-name:pulse}.jiggle.transition{-webkit-animation-duration:750ms;animation-duration:750ms;-webkit-animation-name:jiggle;animation-name:jiggle}@-webkit-keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,100%,50%{opacity:1}25%,75%{opacity:0}}@-webkit-keyframes shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@-webkit-keyframes bounce{0%,100%,20%,50%,80%{-webkit-transform:translateY(0);transform:translateY(0)}40%{-webkit-transform:translateY(-30px);transform:translateY(-30px)}60%{-webkit-transform:translateY(-15px);transform:translateY(-15px)}}@keyframes bounce{0%,100%,20%,50%,80%{-webkit-transform:translateY(0);transform:translateY(0)}40%{-webkit-transform:translateY(-30px);transform:translateY(-30px)}60%{-webkit-transform:translateY(-15px);transform:translateY(-15px)}}@-webkit-keyframes tada{0%{-webkit-transform:scale(1);transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-3deg);transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg);transform:scale(1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg);transform:scale(1.1) rotate(-3deg)}100%{-webkit-transform:scale(1) rotate(0);transform:scale(1) rotate(0)}}@keyframes tada{0%{-webkit-transform:scale(1);transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-3deg);transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg);transform:scale(1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg);transform:scale(1.1) rotate(-3deg)}100%{-webkit-transform:scale(1) rotate(0);transform:scale(1) rotate(0)}}@-webkit-keyframes pulse{0%,100%{-webkit-transform:scale(1);transform:scale(1);opacity:1}50%{-webkit-transform:scale(.9);transform:scale(.9);opacity:.7}}@keyframes pulse{0%,100%{-webkit-transform:scale(1);transform:scale(1);opacity:1}50%{-webkit-transform:scale(.9);transform:scale(.9);opacity:.7}}@-webkit-keyframes jiggle{0%,100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}}@keyframes jiggle{0%,100%{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}} diff --git a/assets/css/themes/basic/assets/fonts/icons.eot b/assets/css/themes/basic/assets/fonts/icons.eot new file mode 100644 index 0000000..25066de Binary files /dev/null and b/assets/css/themes/basic/assets/fonts/icons.eot differ diff --git a/assets/css/themes/basic/assets/fonts/icons.svg b/assets/css/themes/basic/assets/fonts/icons.svg new file mode 100644 index 0000000..b9c54d0 --- /dev/null +++ b/assets/css/themes/basic/assets/fonts/icons.svg @@ -0,0 +1,450 @@ + + + + +Created by FontForge 20100429 at Thu Sep 20 22:09:47 2012 + By root +Copyright (C) 2012 by original authors @ fontello.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/css/themes/basic/assets/fonts/icons.ttf b/assets/css/themes/basic/assets/fonts/icons.ttf new file mode 100644 index 0000000..318a264 Binary files /dev/null and b/assets/css/themes/basic/assets/fonts/icons.ttf differ diff --git a/assets/css/themes/basic/assets/fonts/icons.woff b/assets/css/themes/basic/assets/fonts/icons.woff new file mode 100644 index 0000000..baba1b5 Binary files /dev/null and b/assets/css/themes/basic/assets/fonts/icons.woff differ diff --git a/assets/css/themes/default/assets/fonts/icons.eot b/assets/css/themes/default/assets/fonts/icons.eot new file mode 100644 index 0000000..c7b00d2 Binary files /dev/null and b/assets/css/themes/default/assets/fonts/icons.eot differ diff --git a/assets/css/themes/default/assets/fonts/icons.otf b/assets/css/themes/default/assets/fonts/icons.otf new file mode 100644 index 0000000..f7936cc Binary files /dev/null and b/assets/css/themes/default/assets/fonts/icons.otf differ diff --git a/assets/css/themes/default/assets/fonts/icons.svg b/assets/css/themes/default/assets/fonts/icons.svg new file mode 100644 index 0000000..8b66187 --- /dev/null +++ b/assets/css/themes/default/assets/fonts/icons.svg @@ -0,0 +1,685 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/css/themes/default/assets/fonts/icons.ttf b/assets/css/themes/default/assets/fonts/icons.ttf new file mode 100644 index 0000000..f221e50 Binary files /dev/null and b/assets/css/themes/default/assets/fonts/icons.ttf differ diff --git a/assets/css/themes/default/assets/fonts/icons.woff b/assets/css/themes/default/assets/fonts/icons.woff new file mode 100644 index 0000000..6e7483c Binary files /dev/null and b/assets/css/themes/default/assets/fonts/icons.woff differ diff --git a/assets/css/themes/default/assets/fonts/icons.woff2 b/assets/css/themes/default/assets/fonts/icons.woff2 new file mode 100644 index 0000000..7eb74fd Binary files /dev/null and b/assets/css/themes/default/assets/fonts/icons.woff2 differ diff --git a/assets/css/themes/default/assets/images/flags.png b/assets/css/themes/default/assets/images/flags.png new file mode 100644 index 0000000..cdd33c3 Binary files /dev/null and b/assets/css/themes/default/assets/images/flags.png differ diff --git a/assets/css/themes/github/assets/fonts/octicons-local.ttf b/assets/css/themes/github/assets/fonts/octicons-local.ttf new file mode 100644 index 0000000..d5f4e2e Binary files /dev/null and b/assets/css/themes/github/assets/fonts/octicons-local.ttf differ diff --git a/assets/css/themes/github/assets/fonts/octicons.svg b/assets/css/themes/github/assets/fonts/octicons.svg new file mode 100644 index 0000000..d3116a6 --- /dev/null +++ b/assets/css/themes/github/assets/fonts/octicons.svg @@ -0,0 +1,200 @@ + + + + +(c) 2012-2015 GitHub + +When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos) + +Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) +Applies to all font files + +Code License: MIT (http://choosealicense.com/licenses/mit/) +Applies to all other files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/css/themes/github/assets/fonts/octicons.ttf b/assets/css/themes/github/assets/fonts/octicons.ttf new file mode 100644 index 0000000..9e09105 Binary files /dev/null and b/assets/css/themes/github/assets/fonts/octicons.ttf differ diff --git a/assets/css/themes/github/assets/fonts/octicons.woff b/assets/css/themes/github/assets/fonts/octicons.woff new file mode 100644 index 0000000..cc3c19f Binary files /dev/null and b/assets/css/themes/github/assets/fonts/octicons.woff differ diff --git a/assets/css/themes/material/assets/fonts/icons.eot b/assets/css/themes/material/assets/fonts/icons.eot new file mode 100644 index 0000000..70508eb Binary files /dev/null and b/assets/css/themes/material/assets/fonts/icons.eot differ diff --git a/assets/css/themes/material/assets/fonts/icons.svg b/assets/css/themes/material/assets/fonts/icons.svg new file mode 100644 index 0000000..a449327 --- /dev/null +++ b/assets/css/themes/material/assets/fonts/icons.svg @@ -0,0 +1,2373 @@ + + + + + +Created by FontForge 20151118 at Mon Feb 8 11:58:02 2016 + By shyndman +Copyright 2015 Google, Inc. All Rights Reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/css/themes/material/assets/fonts/icons.ttf b/assets/css/themes/material/assets/fonts/icons.ttf new file mode 100644 index 0000000..7015564 Binary files /dev/null and b/assets/css/themes/material/assets/fonts/icons.ttf differ diff --git a/assets/css/themes/material/assets/fonts/icons.woff b/assets/css/themes/material/assets/fonts/icons.woff new file mode 100644 index 0000000..b648a3e Binary files /dev/null and b/assets/css/themes/material/assets/fonts/icons.woff differ diff --git a/assets/en.js b/assets/en.js new file mode 100644 index 0000000..1020c64 --- /dev/null +++ b/assets/en.js @@ -0,0 +1,391 @@ +// This file was automatically generated. Do not modify. + +'use strict'; + +goog.provide('Blockly.Msg.en'); + +goog.require('Blockly.Msg'); + +Blockly.Msg.ADD_COMMENT = "Add Comment"; +Blockly.Msg.CHANGE_VALUE_TITLE = "Change value:"; +Blockly.Msg.CLEAN_UP = "Clean up Blocks"; +Blockly.Msg.COLLAPSE_ALL = "Collapse Blocks"; +Blockly.Msg.COLLAPSE_BLOCK = "Collapse Block"; +Blockly.Msg.COLOUR_BLEND_COLOUR1 = "colour 1"; +Blockly.Msg.COLOUR_BLEND_COLOUR2 = "colour 2"; +Blockly.Msg.COLOUR_BLEND_HELPURL = "http://meyerweb.com/eric/tools/color-blend/"; +Blockly.Msg.COLOUR_BLEND_RATIO = "ratio"; +Blockly.Msg.COLOUR_BLEND_TITLE = "blend"; +Blockly.Msg.COLOUR_BLEND_TOOLTIP = "Blends two colours together with a given ratio (0.0 - 1.0)."; +Blockly.Msg.COLOUR_PICKER_HELPURL = "https://en.wikipedia.org/wiki/Color"; +Blockly.Msg.COLOUR_PICKER_TOOLTIP = "Choose a colour from the palette."; +Blockly.Msg.COLOUR_RANDOM_HELPURL = "http://randomcolour.com"; +Blockly.Msg.COLOUR_RANDOM_TITLE = "random colour"; +Blockly.Msg.COLOUR_RANDOM_TOOLTIP = "Choose a colour at random."; +Blockly.Msg.COLOUR_RGB_BLUE = "blue"; +Blockly.Msg.COLOUR_RGB_GREEN = "green"; +Blockly.Msg.COLOUR_RGB_HELPURL = "http://www.december.com/html/spec/colorper.html"; +Blockly.Msg.COLOUR_RGB_RED = "red"; +Blockly.Msg.COLOUR_RGB_TITLE = "colour with"; +Blockly.Msg.COLOUR_RGB_TOOLTIP = "Create a colour with the specified amount of red, green, and blue. All values must be between 0 and 100."; +Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL = "https://github.com/google/blockly/wiki/Loops#loop-termination-blocks"; +Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK = "break out of loop"; +Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE = "continue with next iteration of loop"; +Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK = "Break out of the containing loop."; +Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE = "Skip the rest of this loop, and continue with the next iteration."; +Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING = "Warning: This block may only be used within a loop."; +Blockly.Msg.CONTROLS_FOREACH_HELPURL = "https://github.com/google/blockly/wiki/Loops#for-each"; +Blockly.Msg.CONTROLS_FOREACH_TITLE = "for each item %1 in list %2"; +Blockly.Msg.CONTROLS_FOREACH_TOOLTIP = "For each item in a list, set the variable '%1' to the item, and then do some statements."; +Blockly.Msg.CONTROLS_FOR_HELPURL = "https://github.com/google/blockly/wiki/Loops#count-with"; +Blockly.Msg.CONTROLS_FOR_TITLE = "count with %1 from %2 to %3 by %4"; +Blockly.Msg.CONTROLS_FOR_TOOLTIP = "Have the variable '%1' take on the values from the start number to the end number, counting by the specified interval, and do the specified blocks."; +Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP = "Add a condition to the if block."; +Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP = "Add a final, catch-all condition to the if block."; +Blockly.Msg.CONTROLS_IF_HELPURL = "https://github.com/google/blockly/wiki/IfElse"; +Blockly.Msg.CONTROLS_IF_IF_TOOLTIP = "Add, remove, or reorder sections to reconfigure this if block."; +Blockly.Msg.CONTROLS_IF_MSG_ELSE = "else"; +Blockly.Msg.CONTROLS_IF_MSG_ELSEIF = "else if"; +Blockly.Msg.CONTROLS_IF_MSG_IF = "if"; +Blockly.Msg.CONTROLS_IF_TOOLTIP_1 = "If a value is true, then do some statements."; +Blockly.Msg.CONTROLS_IF_TOOLTIP_2 = "If a value is true, then do the first block of statements. Otherwise, do the second block of statements."; +Blockly.Msg.CONTROLS_IF_TOOLTIP_3 = "If the first value is true, then do the first block of statements. Otherwise, if the second value is true, do the second block of statements."; +Blockly.Msg.CONTROLS_IF_TOOLTIP_4 = "If the first value is true, then do the first block of statements. Otherwise, if the second value is true, do the second block of statements. If none of the values are true, do the last block of statements."; +Blockly.Msg.CONTROLS_REPEAT_HELPURL = "https://en.wikipedia.org/wiki/For_loop"; +Blockly.Msg.CONTROLS_REPEAT_INPUT_DO = "do"; +Blockly.Msg.CONTROLS_REPEAT_TITLE = "repeat %1 times"; +Blockly.Msg.CONTROLS_REPEAT_TOOLTIP = "Do some statements several times."; +Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL = "https://github.com/google/blockly/wiki/Loops#repeat"; +Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL = "repeat until"; +Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE = "repeat while"; +Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL = "While a value is false, then do some statements."; +Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE = "While a value is true, then do some statements."; +Blockly.Msg.DELETE_ALL_BLOCKS = "Delete all %1 blocks?"; +Blockly.Msg.DELETE_BLOCK = "Delete Block"; +Blockly.Msg.DELETE_VARIABLE = "Delete the '%1' variable"; +Blockly.Msg.DELETE_VARIABLE_CONFIRMATION = "Delete %1 uses of the '%2' variable?"; +Blockly.Msg.DELETE_X_BLOCKS = "Delete %1 Blocks"; +Blockly.Msg.DISABLE_BLOCK = "Disable Block"; +Blockly.Msg.DUPLICATE_BLOCK = "Duplicate"; +Blockly.Msg.ENABLE_BLOCK = "Enable Block"; +Blockly.Msg.EXPAND_ALL = "Expand Blocks"; +Blockly.Msg.EXPAND_BLOCK = "Expand Block"; +Blockly.Msg.EXTERNAL_INPUTS = "External Inputs"; +Blockly.Msg.HELP = "Help"; +Blockly.Msg.INLINE_INPUTS = "Inline Inputs"; +Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL = "https://github.com/google/blockly/wiki/Lists#create-empty-list"; +Blockly.Msg.LISTS_CREATE_EMPTY_TITLE = "create empty list"; +Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP = "Returns a list, of length 0, containing no data records"; +Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD = "list"; +Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP = "Add, remove, or reorder sections to reconfigure this list block."; +Blockly.Msg.LISTS_CREATE_WITH_HELPURL = "https://github.com/google/blockly/wiki/Lists#create-list-with"; +Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH = "create list with"; +Blockly.Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP = "Add an item to the list."; +Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP = "Create a list with any number of items."; +Blockly.Msg.LISTS_GET_INDEX_FIRST = "first"; +Blockly.Msg.LISTS_GET_INDEX_FROM_END = "# from end"; +Blockly.Msg.LISTS_GET_INDEX_FROM_START = "#"; +Blockly.Msg.LISTS_GET_INDEX_GET = "get"; +Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE = "get and remove"; +Blockly.Msg.LISTS_GET_INDEX_LAST = "last"; +Blockly.Msg.LISTS_GET_INDEX_RANDOM = "random"; +Blockly.Msg.LISTS_GET_INDEX_REMOVE = "remove"; +Blockly.Msg.LISTS_GET_INDEX_TAIL = ""; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST = "Returns the first item in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM = "Returns the item at the specified position in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST = "Returns the last item in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM = "Returns a random item in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST = "Removes and returns the first item in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM = "Removes and returns the item at the specified position in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST = "Removes and returns the last item in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM = "Removes and returns a random item in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST = "Removes the first item in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM = "Removes the item at the specified position in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST = "Removes the last item in a list."; +Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM = "Removes a random item in a list."; +Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END = "to # from end"; +Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START = "to #"; +Blockly.Msg.LISTS_GET_SUBLIST_END_LAST = "to last"; +Blockly.Msg.LISTS_GET_SUBLIST_HELPURL = "https://github.com/google/blockly/wiki/Lists#getting-a-sublist"; +Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST = "get sub-list from first"; +Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END = "get sub-list from # from end"; +Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START = "get sub-list from #"; +Blockly.Msg.LISTS_GET_SUBLIST_TAIL = ""; +Blockly.Msg.LISTS_GET_SUBLIST_TOOLTIP = "Creates a copy of the specified portion of a list."; +Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP = "%1 is the last item."; +Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP = "%1 is the first item."; +Blockly.Msg.LISTS_INDEX_OF_FIRST = "find first occurrence of item"; +Blockly.Msg.LISTS_INDEX_OF_HELPURL = "https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list"; +Blockly.Msg.LISTS_INDEX_OF_LAST = "find last occurrence of item"; +Blockly.Msg.LISTS_INDEX_OF_TOOLTIP = "Returns the index of the first/last occurrence of the item in the list. Returns %1 if item is not found."; +Blockly.Msg.LISTS_INLIST = "in list"; +Blockly.Msg.LISTS_ISEMPTY_HELPURL = "https://github.com/google/blockly/wiki/Lists#is-empty"; +Blockly.Msg.LISTS_ISEMPTY_TITLE = "%1 is empty"; +Blockly.Msg.LISTS_ISEMPTY_TOOLTIP = "Returns true if the list is empty."; +Blockly.Msg.LISTS_LENGTH_HELPURL = "https://github.com/google/blockly/wiki/Lists#length-of"; +Blockly.Msg.LISTS_LENGTH_TITLE = "length of %1"; +Blockly.Msg.LISTS_LENGTH_TOOLTIP = "Returns the length of a list."; +Blockly.Msg.LISTS_REPEAT_HELPURL = "https://github.com/google/blockly/wiki/Lists#create-list-with"; +Blockly.Msg.LISTS_REPEAT_TITLE = "create list with item %1 repeated %2 times"; +Blockly.Msg.LISTS_REPEAT_TOOLTIP = "Creates a list consisting of the given value repeated the specified number of times."; +Blockly.Msg.LISTS_SET_INDEX_HELPURL = "https://github.com/google/blockly/wiki/Lists#in-list--set"; +Blockly.Msg.LISTS_SET_INDEX_INPUT_TO = "as"; +Blockly.Msg.LISTS_SET_INDEX_INSERT = "insert at"; +Blockly.Msg.LISTS_SET_INDEX_SET = "set"; +Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST = "Inserts the item at the start of a list."; +Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM = "Inserts the item at the specified position in a list."; +Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST = "Append the item to the end of a list."; +Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM = "Inserts the item randomly in a list."; +Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST = "Sets the first item in a list."; +Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM = "Sets the item at the specified position in a list."; +Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST = "Sets the last item in a list."; +Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM = "Sets a random item in a list."; +Blockly.Msg.LISTS_SORT_HELPURL = "https://github.com/google/blockly/wiki/Lists#sorting-a-list"; +Blockly.Msg.LISTS_SORT_ORDER_ASCENDING = "ascending"; +Blockly.Msg.LISTS_SORT_ORDER_DESCENDING = "descending"; +Blockly.Msg.LISTS_SORT_TITLE = "sort %1 %2 %3"; +Blockly.Msg.LISTS_SORT_TOOLTIP = "Sort a copy of a list."; +Blockly.Msg.LISTS_SORT_TYPE_IGNORECASE = "alphabetic, ignore case"; +Blockly.Msg.LISTS_SORT_TYPE_NUMERIC = "numeric"; +Blockly.Msg.LISTS_SORT_TYPE_TEXT = "alphabetic"; +Blockly.Msg.LISTS_SPLIT_HELPURL = "https://github.com/google/blockly/wiki/Lists#splitting-strings-and-joining-lists"; +Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT = "make list from text"; +Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST = "make text from list"; +Blockly.Msg.LISTS_SPLIT_TOOLTIP_JOIN = "Join a list of texts into one text, separated by a delimiter."; +Blockly.Msg.LISTS_SPLIT_TOOLTIP_SPLIT = "Split text into a list of texts, breaking at each delimiter."; +Blockly.Msg.LISTS_SPLIT_WITH_DELIMITER = "with delimiter"; +Blockly.Msg.LOGIC_BOOLEAN_FALSE = "false"; +Blockly.Msg.LOGIC_BOOLEAN_HELPURL = "https://github.com/google/blockly/wiki/Logic#values"; +Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP = "Returns either true or false."; +Blockly.Msg.LOGIC_BOOLEAN_TRUE = "true"; +Blockly.Msg.LOGIC_COMPARE_HELPURL = "https://en.wikipedia.org/wiki/Inequality_(mathematics)"; +Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ = "Return true if both inputs equal each other."; +Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT = "Return true if the first input is greater than the second input."; +Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE = "Return true if the first input is greater than or equal to the second input."; +Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT = "Return true if the first input is smaller than the second input."; +Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE = "Return true if the first input is smaller than or equal to the second input."; +Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ = "Return true if both inputs are not equal to each other."; +Blockly.Msg.LOGIC_NEGATE_HELPURL = "https://github.com/google/blockly/wiki/Logic#not"; +Blockly.Msg.LOGIC_NEGATE_TITLE = "not %1"; +Blockly.Msg.LOGIC_NEGATE_TOOLTIP = "Returns true if the input is false. Returns false if the input is true."; +Blockly.Msg.LOGIC_NULL = "null"; +Blockly.Msg.LOGIC_NULL_HELPURL = "https://en.wikipedia.org/wiki/Nullable_type"; +Blockly.Msg.LOGIC_NULL_TOOLTIP = "Returns null."; +Blockly.Msg.LOGIC_OPERATION_AND = "and"; +Blockly.Msg.LOGIC_OPERATION_HELPURL = "https://github.com/google/blockly/wiki/Logic#logical-operations"; +Blockly.Msg.LOGIC_OPERATION_OR = "or"; +Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND = "Return true if both inputs are true."; +Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR = "Return true if at least one of the inputs is true."; +Blockly.Msg.LOGIC_TERNARY_CONDITION = "test"; +Blockly.Msg.LOGIC_TERNARY_HELPURL = "https://en.wikipedia.org/wiki/%3F:"; +Blockly.Msg.LOGIC_TERNARY_IF_FALSE = "if false"; +Blockly.Msg.LOGIC_TERNARY_IF_TRUE = "if true"; +Blockly.Msg.LOGIC_TERNARY_TOOLTIP = "Check the condition in 'test'. If the condition is true, returns the 'if true' value; otherwise returns the 'if false' value."; +Blockly.Msg.MATH_ADDITION_SYMBOL = "+"; +Blockly.Msg.MATH_ARITHMETIC_HELPURL = "https://en.wikipedia.org/wiki/Arithmetic"; +Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD = "Return the sum of the two numbers."; +Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE = "Return the quotient of the two numbers."; +Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MINUS = "Return the difference of the two numbers."; +Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY = "Return the product of the two numbers."; +Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER = "Return the first number raised to the power of the second number."; +Blockly.Msg.MATH_CHANGE_HELPURL = "https://en.wikipedia.org/wiki/Programming_idiom#Incrementing_a_counter"; +Blockly.Msg.MATH_CHANGE_TITLE = "change %1 by %2"; +Blockly.Msg.MATH_CHANGE_TOOLTIP = "Add a number to variable '%1'."; +Blockly.Msg.MATH_CONSTANT_HELPURL = "https://en.wikipedia.org/wiki/Mathematical_constant"; +Blockly.Msg.MATH_CONSTANT_TOOLTIP = "Return one of the common constants: π (3.141…), e (2.718…), φ (1.618…), sqrt(2) (1.414…), sqrt(½) (0.707…), or ∞ (infinity)."; +Blockly.Msg.MATH_CONSTRAIN_HELPURL = "https://en.wikipedia.org/wiki/Clamping_%28graphics%29"; +Blockly.Msg.MATH_CONSTRAIN_TITLE = "constrain %1 low %2 high %3"; +Blockly.Msg.MATH_CONSTRAIN_TOOLTIP = "Constrain a number to be between the specified limits (inclusive)."; +Blockly.Msg.MATH_DIVISION_SYMBOL = "÷"; +Blockly.Msg.MATH_IS_DIVISIBLE_BY = "is divisible by"; +Blockly.Msg.MATH_IS_EVEN = "is even"; +Blockly.Msg.MATH_IS_NEGATIVE = "is negative"; +Blockly.Msg.MATH_IS_ODD = "is odd"; +Blockly.Msg.MATH_IS_POSITIVE = "is positive"; +Blockly.Msg.MATH_IS_PRIME = "is prime"; +Blockly.Msg.MATH_IS_TOOLTIP = "Check if a number is an even, odd, prime, whole, positive, negative, or if it is divisible by certain number. Returns true or false."; +Blockly.Msg.MATH_IS_WHOLE = "is whole"; +Blockly.Msg.MATH_MODULO_HELPURL = "https://en.wikipedia.org/wiki/Modulo_operation"; +Blockly.Msg.MATH_MODULO_TITLE = "remainder of %1 ÷ %2"; +Blockly.Msg.MATH_MODULO_TOOLTIP = "Return the remainder from dividing the two numbers."; +Blockly.Msg.MATH_MULTIPLICATION_SYMBOL = "×"; +Blockly.Msg.MATH_NUMBER_HELPURL = "https://en.wikipedia.org/wiki/Number"; +Blockly.Msg.MATH_NUMBER_TOOLTIP = "A number."; +Blockly.Msg.MATH_ONLIST_HELPURL = ""; +Blockly.Msg.MATH_ONLIST_OPERATOR_AVERAGE = "average of list"; +Blockly.Msg.MATH_ONLIST_OPERATOR_MAX = "max of list"; +Blockly.Msg.MATH_ONLIST_OPERATOR_MEDIAN = "median of list"; +Blockly.Msg.MATH_ONLIST_OPERATOR_MIN = "min of list"; +Blockly.Msg.MATH_ONLIST_OPERATOR_MODE = "modes of list"; +Blockly.Msg.MATH_ONLIST_OPERATOR_RANDOM = "random item of list"; +Blockly.Msg.MATH_ONLIST_OPERATOR_STD_DEV = "standard deviation of list"; +Blockly.Msg.MATH_ONLIST_OPERATOR_SUM = "sum of list"; +Blockly.Msg.MATH_ONLIST_TOOLTIP_AVERAGE = "Return the average (arithmetic mean) of the numeric values in the list."; +Blockly.Msg.MATH_ONLIST_TOOLTIP_MAX = "Return the largest number in the list."; +Blockly.Msg.MATH_ONLIST_TOOLTIP_MEDIAN = "Return the median number in the list."; +Blockly.Msg.MATH_ONLIST_TOOLTIP_MIN = "Return the smallest number in the list."; +Blockly.Msg.MATH_ONLIST_TOOLTIP_MODE = "Return a list of the most common item(s) in the list."; +Blockly.Msg.MATH_ONLIST_TOOLTIP_RANDOM = "Return a random element from the list."; +Blockly.Msg.MATH_ONLIST_TOOLTIP_STD_DEV = "Return the standard deviation of the list."; +Blockly.Msg.MATH_ONLIST_TOOLTIP_SUM = "Return the sum of all the numbers in the list."; +Blockly.Msg.MATH_POWER_SYMBOL = "^"; +Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL = "https://en.wikipedia.org/wiki/Random_number_generation"; +Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM = "random fraction"; +Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP = "Return a random fraction between 0.0 (inclusive) and 1.0 (exclusive)."; +Blockly.Msg.MATH_RANDOM_INT_HELPURL = "https://en.wikipedia.org/wiki/Random_number_generation"; +Blockly.Msg.MATH_RANDOM_INT_TITLE = "random integer from %1 to %2"; +Blockly.Msg.MATH_RANDOM_INT_TOOLTIP = "Return a random integer between the two specified limits, inclusive."; +Blockly.Msg.MATH_ROUND_HELPURL = "https://en.wikipedia.org/wiki/Rounding"; +Blockly.Msg.MATH_ROUND_OPERATOR_ROUND = "round"; +Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDDOWN = "round down"; +Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDUP = "round up"; +Blockly.Msg.MATH_ROUND_TOOLTIP = "Round a number up or down."; +Blockly.Msg.MATH_SINGLE_HELPURL = "https://en.wikipedia.org/wiki/Square_root"; +Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE = "absolute"; +Blockly.Msg.MATH_SINGLE_OP_ROOT = "square root"; +Blockly.Msg.MATH_SINGLE_TOOLTIP_ABS = "Return the absolute value of a number."; +Blockly.Msg.MATH_SINGLE_TOOLTIP_EXP = "Return e to the power of a number."; +Blockly.Msg.MATH_SINGLE_TOOLTIP_LN = "Return the natural logarithm of a number."; +Blockly.Msg.MATH_SINGLE_TOOLTIP_LOG10 = "Return the base 10 logarithm of a number."; +Blockly.Msg.MATH_SINGLE_TOOLTIP_NEG = "Return the negation of a number."; +Blockly.Msg.MATH_SINGLE_TOOLTIP_POW10 = "Return 10 to the power of a number."; +Blockly.Msg.MATH_SINGLE_TOOLTIP_ROOT = "Return the square root of a number."; +Blockly.Msg.MATH_SUBTRACTION_SYMBOL = "-"; +Blockly.Msg.MATH_TRIG_ACOS = "acos"; +Blockly.Msg.MATH_TRIG_ASIN = "asin"; +Blockly.Msg.MATH_TRIG_ATAN = "atan"; +Blockly.Msg.MATH_TRIG_COS = "cos"; +Blockly.Msg.MATH_TRIG_HELPURL = "https://en.wikipedia.org/wiki/Trigonometric_functions"; +Blockly.Msg.MATH_TRIG_SIN = "sin"; +Blockly.Msg.MATH_TRIG_TAN = "tan"; +Blockly.Msg.MATH_TRIG_TOOLTIP_ACOS = "Return the arccosine of a number."; +Blockly.Msg.MATH_TRIG_TOOLTIP_ASIN = "Return the arcsine of a number."; +Blockly.Msg.MATH_TRIG_TOOLTIP_ATAN = "Return the arctangent of a number."; +Blockly.Msg.MATH_TRIG_TOOLTIP_COS = "Return the cosine of a degree (not radian)."; +Blockly.Msg.MATH_TRIG_TOOLTIP_SIN = "Return the sine of a degree (not radian)."; +Blockly.Msg.MATH_TRIG_TOOLTIP_TAN = "Return the tangent of a degree (not radian)."; +Blockly.Msg.NEW_VARIABLE = "Create variable..."; +Blockly.Msg.NEW_VARIABLE_TITLE = "New variable name:"; +Blockly.Msg.ORDINAL_NUMBER_SUFFIX = ""; +Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS = "allow statements"; +Blockly.Msg.PROCEDURES_BEFORE_PARAMS = "with:"; +Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL = "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29"; +Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP = "Run the user-defined function '%1'."; +Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL = "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29"; +Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP = "Run the user-defined function '%1' and use its output."; +Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS = "with:"; +Blockly.Msg.PROCEDURES_CREATE_DO = "Create '%1'"; +Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT = "Describe this function..."; +Blockly.Msg.PROCEDURES_DEFNORETURN_DO = ""; +Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL = "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29"; +Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE = "do something"; +Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE = "to"; +Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP = "Creates a function with no output."; +Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL = "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29"; +Blockly.Msg.PROCEDURES_DEFRETURN_RETURN = "return"; +Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP = "Creates a function with an output."; +Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING = "Warning: This function has duplicate parameters."; +Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF = "Highlight function definition"; +Blockly.Msg.PROCEDURES_IFRETURN_HELPURL = "http://c2.com/cgi/wiki?GuardClause"; +Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP = "If a value is true, then return a second value."; +Blockly.Msg.PROCEDURES_IFRETURN_WARNING = "Warning: This block may be used only within a function definition."; +Blockly.Msg.PROCEDURES_MUTATORARG_TITLE = "input name:"; +Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP = "Add an input to the function."; +Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE = "inputs"; +Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP = "Add, remove, or reorder inputs to this function."; +Blockly.Msg.REDO = "Redo"; +Blockly.Msg.REMOVE_COMMENT = "Remove Comment"; +Blockly.Msg.RENAME_VARIABLE = "Rename variable..."; +Blockly.Msg.RENAME_VARIABLE_TITLE = "Rename all '%1' variables to:"; +Blockly.Msg.TEXT_APPEND_APPENDTEXT = "append text"; +Blockly.Msg.TEXT_APPEND_HELPURL = "https://github.com/google/blockly/wiki/Text#text-modification"; +Blockly.Msg.TEXT_APPEND_TO = "to"; +Blockly.Msg.TEXT_APPEND_TOOLTIP = "Append some text to variable '%1'."; +Blockly.Msg.TEXT_CHANGECASE_HELPURL = "https://github.com/google/blockly/wiki/Text#adjusting-text-case"; +Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE = "to lower case"; +Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE = "to Title Case"; +Blockly.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE = "to UPPER CASE"; +Blockly.Msg.TEXT_CHANGECASE_TOOLTIP = "Return a copy of the text in a different case."; +Blockly.Msg.TEXT_CHARAT_FIRST = "get first letter"; +Blockly.Msg.TEXT_CHARAT_FROM_END = "get letter # from end"; +Blockly.Msg.TEXT_CHARAT_FROM_START = "get letter #"; +Blockly.Msg.TEXT_CHARAT_HELPURL = "https://github.com/google/blockly/wiki/Text#extracting-text"; +Blockly.Msg.TEXT_CHARAT_INPUT_INTEXT = "in text"; +Blockly.Msg.TEXT_CHARAT_LAST = "get last letter"; +Blockly.Msg.TEXT_CHARAT_RANDOM = "get random letter"; +Blockly.Msg.TEXT_CHARAT_TAIL = ""; +Blockly.Msg.TEXT_CHARAT_TOOLTIP = "Returns the letter at the specified position."; +Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TOOLTIP = "Add an item to the text."; +Blockly.Msg.TEXT_CREATE_JOIN_TITLE_JOIN = "join"; +Blockly.Msg.TEXT_CREATE_JOIN_TOOLTIP = "Add, remove, or reorder sections to reconfigure this text block."; +Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END = "to letter # from end"; +Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_START = "to letter #"; +Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST = "to last letter"; +Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL = "https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text"; +Blockly.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT = "in text"; +Blockly.Msg.TEXT_GET_SUBSTRING_START_FIRST = "get substring from first letter"; +Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_END = "get substring from letter # from end"; +Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START = "get substring from letter #"; +Blockly.Msg.TEXT_GET_SUBSTRING_TAIL = ""; +Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP = "Returns a specified portion of the text."; +Blockly.Msg.TEXT_INDEXOF_HELPURL = "https://github.com/google/blockly/wiki/Text#finding-text"; +Blockly.Msg.TEXT_INDEXOF_INPUT_INTEXT = "in text"; +Blockly.Msg.TEXT_INDEXOF_OPERATOR_FIRST = "find first occurrence of text"; +Blockly.Msg.TEXT_INDEXOF_OPERATOR_LAST = "find last occurrence of text"; +Blockly.Msg.TEXT_INDEXOF_TAIL = ""; +Blockly.Msg.TEXT_INDEXOF_TOOLTIP = "Returns the index of the first/last occurrence of the first text in the second text. Returns %1 if text is not found."; +Blockly.Msg.TEXT_ISEMPTY_HELPURL = "https://github.com/google/blockly/wiki/Text#checking-for-empty-text"; +Blockly.Msg.TEXT_ISEMPTY_TITLE = "%1 is empty"; +Blockly.Msg.TEXT_ISEMPTY_TOOLTIP = "Returns true if the provided text is empty."; +Blockly.Msg.TEXT_JOIN_HELPURL = "https://github.com/google/blockly/wiki/Text#text-creation"; +Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH = "create text with"; +Blockly.Msg.TEXT_JOIN_TOOLTIP = "Create a piece of text by joining together any number of items."; +Blockly.Msg.TEXT_LENGTH_HELPURL = "https://github.com/google/blockly/wiki/Text#text-modification"; +Blockly.Msg.TEXT_LENGTH_TITLE = "length of %1"; +Blockly.Msg.TEXT_LENGTH_TOOLTIP = "Returns the number of letters (including spaces) in the provided text."; +Blockly.Msg.TEXT_PRINT_HELPURL = "https://github.com/google/blockly/wiki/Text#printing-text"; +Blockly.Msg.TEXT_PRINT_TITLE = "print %1"; +Blockly.Msg.TEXT_PRINT_TOOLTIP = "Print the specified text, number or other value."; +Blockly.Msg.TEXT_PROMPT_HELPURL = "https://github.com/google/blockly/wiki/Text#getting-input-from-the-user"; +Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER = "Prompt for user for a number."; +Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT = "Prompt for user for some text."; +Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER = "prompt for number with message"; +Blockly.Msg.TEXT_PROMPT_TYPE_TEXT = "prompt for text with message"; +Blockly.Msg.TEXT_TEXT_HELPURL = "https://en.wikipedia.org/wiki/String_(computer_science)"; +Blockly.Msg.TEXT_TEXT_TOOLTIP = "A letter, word, or line of text."; +Blockly.Msg.TEXT_TRIM_HELPURL = "https://github.com/google/blockly/wiki/Text#trimming-removing-spaces"; +Blockly.Msg.TEXT_TRIM_OPERATOR_BOTH = "trim spaces from both sides of"; +Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT = "trim spaces from left side of"; +Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT = "trim spaces from right side of"; +Blockly.Msg.TEXT_TRIM_TOOLTIP = "Return a copy of the text with spaces removed from one or both ends."; +Blockly.Msg.TODAY = "Today"; +Blockly.Msg.UNDO = "Undo"; +Blockly.Msg.VARIABLES_DEFAULT_NAME = "item"; +Blockly.Msg.VARIABLES_GET_CREATE_SET = "Create 'set %1'"; +Blockly.Msg.VARIABLES_GET_HELPURL = "https://github.com/google/blockly/wiki/Variables#get"; +Blockly.Msg.VARIABLES_GET_TOOLTIP = "Returns the value of this variable."; +Blockly.Msg.VARIABLES_SET = "set %1 to %2"; +Blockly.Msg.VARIABLES_SET_CREATE_GET = "Create 'get %1'"; +Blockly.Msg.VARIABLES_SET_HELPURL = "https://github.com/google/blockly/wiki/Variables#set"; +Blockly.Msg.VARIABLES_SET_TOOLTIP = "Sets this variable to be equal to the input."; +Blockly.Msg.VARIABLE_ALREADY_EXISTS = "A variable named '%1' already exists."; +Blockly.Msg.PROCEDURES_DEFRETURN_TITLE = Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE; +Blockly.Msg.CONTROLS_IF_IF_TITLE_IF = Blockly.Msg.CONTROLS_IF_MSG_IF; +Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; +Blockly.Msg.CONTROLS_IF_MSG_THEN = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; +Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE = Blockly.Msg.CONTROLS_IF_MSG_ELSE; +Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE = Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE; +Blockly.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; +Blockly.Msg.LISTS_GET_INDEX_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; +Blockly.Msg.MATH_CHANGE_TITLE_ITEM = Blockly.Msg.VARIABLES_DEFAULT_NAME; +Blockly.Msg.PROCEDURES_DEFRETURN_DO = Blockly.Msg.PROCEDURES_DEFNORETURN_DO; +Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF = Blockly.Msg.CONTROLS_IF_MSG_ELSEIF; +Blockly.Msg.LISTS_GET_INDEX_HELPURL = Blockly.Msg.LISTS_INDEX_OF_HELPURL; +Blockly.Msg.CONTROLS_FOREACH_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; +Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; +Blockly.Msg.CONTROLS_FOR_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; +Blockly.Msg.LISTS_CREATE_WITH_ITEM_TITLE = Blockly.Msg.VARIABLES_DEFAULT_NAME; +Blockly.Msg.TEXT_APPEND_VARIABLE = Blockly.Msg.VARIABLES_DEFAULT_NAME; +Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TITLE_ITEM = Blockly.Msg.VARIABLES_DEFAULT_NAME; +Blockly.Msg.LISTS_INDEX_OF_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; +Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT = Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT; \ No newline at end of file diff --git a/assets/media/1x1.gif b/assets/media/1x1.gif new file mode 100644 index 0000000..3085511 Binary files /dev/null and b/assets/media/1x1.gif differ diff --git a/assets/media/click.mp3 b/assets/media/click.mp3 new file mode 100644 index 0000000..4534b0d Binary files /dev/null and b/assets/media/click.mp3 differ diff --git a/assets/media/click.ogg b/assets/media/click.ogg new file mode 100644 index 0000000..e8ae42a Binary files /dev/null and b/assets/media/click.ogg differ diff --git a/assets/media/click.wav b/assets/media/click.wav new file mode 100644 index 0000000..41a50cd Binary files /dev/null and b/assets/media/click.wav differ diff --git a/assets/media/delete.mp3 b/assets/media/delete.mp3 new file mode 100644 index 0000000..442bd9c Binary files /dev/null and b/assets/media/delete.mp3 differ diff --git a/assets/media/delete.ogg b/assets/media/delete.ogg new file mode 100644 index 0000000..67f84ac Binary files /dev/null and b/assets/media/delete.ogg differ diff --git a/assets/media/delete.wav b/assets/media/delete.wav new file mode 100644 index 0000000..18debcf Binary files /dev/null and b/assets/media/delete.wav differ diff --git a/assets/media/disconnect.mp3 b/assets/media/disconnect.mp3 new file mode 100644 index 0000000..8cfaff6 Binary files /dev/null and b/assets/media/disconnect.mp3 differ diff --git a/assets/media/disconnect.ogg b/assets/media/disconnect.ogg new file mode 100644 index 0000000..467b527 Binary files /dev/null and b/assets/media/disconnect.ogg differ diff --git a/assets/media/disconnect.wav b/assets/media/disconnect.wav new file mode 100644 index 0000000..af5c254 Binary files /dev/null and b/assets/media/disconnect.wav differ diff --git a/assets/media/handclosed.cur b/assets/media/handclosed.cur new file mode 100644 index 0000000..4851755 Binary files /dev/null and b/assets/media/handclosed.cur differ diff --git a/assets/media/handdelete.cur b/assets/media/handdelete.cur new file mode 100644 index 0000000..170320f Binary files /dev/null and b/assets/media/handdelete.cur differ diff --git a/assets/media/handopen.cur b/assets/media/handopen.cur new file mode 100644 index 0000000..da44588 Binary files /dev/null and b/assets/media/handopen.cur differ diff --git a/assets/media/quote0.png b/assets/media/quote0.png new file mode 100644 index 0000000..b2fd10f Binary files /dev/null and b/assets/media/quote0.png differ diff --git a/assets/media/quote1.png b/assets/media/quote1.png new file mode 100644 index 0000000..826583e Binary files /dev/null and b/assets/media/quote1.png differ diff --git a/assets/media/sprites.png b/assets/media/sprites.png new file mode 100644 index 0000000..7f704a5 Binary files /dev/null and b/assets/media/sprites.png differ diff --git a/assets/media/sprites.svg b/assets/media/sprites.svg new file mode 100644 index 0000000..3f09ef3 --- /dev/null +++ b/assets/media/sprites.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.boot b/build.boot index 8653396..159bd20 100644 --- a/build.boot +++ b/build.boot @@ -1,10 +1,11 @@ (set-env! :dependencies '[[adzerk/boot-cljs "1.7.228-2"] [adzerk/boot-cljs-repl "0.3.3"] + [cljsjs/jquery "2.2.2-0"] [com.cemerick/piggieback "0.2.1" :scope "test"] [compojure "1.5.1"] [hoplon/castra "3.0.0-alpha5"] - [hoplon "6.0.0-alpha17"] + [hoplon "6.0.0-alpha17.amatus0"] [org.clojure/clojure "1.7.0"] [org.clojure/clojurescript "1.7.170"] [org.clojure/tools.nrepl "0.2.12" :scope "test"] diff --git a/generate_deps.py b/generate_deps.py new file mode 100644 index 0000000..061cea3 --- /dev/null +++ b/generate_deps.py @@ -0,0 +1,57 @@ +class goog: + @staticmethod + def addDependency(path, provides, requires): + print '{:file "%s"' % path[9:] + print ' :provides [' + ' '.join('"%s"' % p for p in provides) + ']' + print ' :requires [' + ' '.join('"%s"' % r for r in requires) + ']}' + +dir = 'blockly'; +goog.addDependency("../../../" + dir + "/core/warning.js", ['Blockly.Warning'], ['Blockly.Bubble', 'Blockly.Icon']); +goog.addDependency("../../../" + dir + "/core/field.js", ['Blockly.Field'], ['goog.asserts', 'goog.dom', 'goog.math.Size', 'goog.style', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/scrollbar.js", ['Blockly.Scrollbar', 'Blockly.ScrollbarPair'], ['goog.dom', 'goog.events']); +goog.addDependency("../../../" + dir + "/core/comment.js", ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Icon', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/events.js", ['Blockly.Events'], ['goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/trashcan.js", ['Blockly.Trashcan'], ['goog.Timer', 'goog.dom', 'goog.math', 'goog.math.Rect']); +goog.addDependency("../../../" + dir + "/core/connection.js", ['Blockly.Connection'], ['goog.asserts', 'goog.dom']); +goog.addDependency("../../../" + dir + "/core/widgetdiv.js", ['Blockly.WidgetDiv'], ['Blockly.Css', 'goog.dom', 'goog.dom.TagName', 'goog.style']); +goog.addDependency("../../../" + dir + "/core/blockly.js", ['Blockly'], ['Blockly.BlockSvg.render', 'Blockly.Events', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldTextInput', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.Generator', 'Blockly.Msg', 'Blockly.Procedures', 'Blockly.Toolbox', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.constants', 'Blockly.inject', 'Blockly.utils', 'goog.color', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/flyout_button.js", ['Blockly.FlyoutButton'], ['goog.dom', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/block_render_svg.js", ['Blockly.BlockSvg.render'], ['Blockly.BlockSvg']); +goog.addDependency("../../../" + dir + "/core/utils.js", ['Blockly.utils'], ['goog.dom', 'goog.events.BrowserFeature', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/msg.js", ['Blockly.Msg'], []); +goog.addDependency("../../../" + dir + "/core/contextmenu.js", ['Blockly.ContextMenu'], ['goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem']); +goog.addDependency("../../../" + dir + "/core/icon.js", ['Blockly.Icon'], ['goog.dom', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/field_textinput.js", ['Blockly.FieldTextInput'], ['Blockly.Field', 'Blockly.Msg', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/toolbox.js", ['Blockly.Toolbox'], ['Blockly.Flyout', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.BrowserFeature', 'goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.math.Rect', 'goog.style', 'goog.ui.tree.TreeControl', 'goog.ui.tree.TreeNode']); +goog.addDependency("../../../" + dir + "/core/options.js", ['Blockly.Options'], []); +goog.addDependency("../../../" + dir + "/core/block.js", ['Blockly.Block'], ['Blockly.Blocks', 'Blockly.Comment', 'Blockly.Connection', 'Blockly.Input', 'Blockly.Mutator', 'Blockly.Warning', 'Blockly.Workspace', 'Blockly.Xml', 'goog.array', 'goog.asserts', 'goog.math.Coordinate', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/block_svg.js", ['Blockly.BlockSvg'], ['Blockly.Block', 'Blockly.ContextMenu', 'Blockly.RenderedConnection', 'goog.Timer', 'goog.asserts', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/field_dropdown.js", ['Blockly.FieldDropdown'], ['Blockly.Field', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/css.js", ['Blockly.Css'], []); +goog.addDependency("../../../" + dir + "/core/field_checkbox.js", ['Blockly.FieldCheckbox'], ['Blockly.Field']); +goog.addDependency("../../../" + dir + "/core/field_label.js", ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.Tooltip', 'goog.dom', 'goog.math.Size']); +goog.addDependency("../../../" + dir + "/core/names.js", ['Blockly.Names'], []); +goog.addDependency("../../../" + dir + "/core/mutator.js", ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Icon', 'Blockly.WorkspaceSvg', 'goog.Timer', 'goog.dom']); +goog.addDependency("../../../" + dir + "/core/constants.js", ['Blockly.constants'], []); +goog.addDependency("../../../" + dir + "/core/rendered_connection.js", ['Blockly.RenderedConnection'], ['Blockly.Connection']); +goog.addDependency("../../../" + dir + "/core/field_colour.js", ['Blockly.FieldColour'], ['Blockly.Field', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.ColorPicker']); +goog.addDependency("../../../" + dir + "/core/field_image.js", ['Blockly.FieldImage'], ['Blockly.Field', 'goog.dom', 'goog.math.Size', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/field_variable.js", ['Blockly.FieldVariable'], ['Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.Variables', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/input.js", ['Blockly.Input'], ['Blockly.Connection', 'Blockly.FieldLabel', 'goog.asserts']); +goog.addDependency("../../../" + dir + "/core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'goog.math']); +goog.addDependency("../../../" + dir + "/core/variables.js", ['Blockly.Variables'], ['Blockly.Blocks', 'Blockly.Workspace', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/workspace_svg.js", ['Blockly.WorkspaceSvg'], ['Blockly.ConnectionDB', 'Blockly.constants', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Trashcan', 'Blockly.Workspace', 'Blockly.Xml', 'Blockly.ZoomControls', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/bubble.js", ['Blockly.Bubble'], ['Blockly.Workspace', 'goog.dom', 'goog.math', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/procedures.js", ['Blockly.Procedures'], ['Blockly.Blocks', 'Blockly.Field', 'Blockly.Names', 'Blockly.Workspace']); +goog.addDependency("../../../" + dir + "/core/flyout.js", ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Comment', 'Blockly.Events', 'Blockly.FlyoutButton', 'Blockly.WorkspaceSvg', 'goog.dom', 'goog.events', 'goog.math.Rect', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/xml.js", ['Blockly.Xml'], ['goog.asserts', 'goog.dom']); +goog.addDependency("../../../" + dir + "/core/blocks.js", ['Blockly.Blocks'], []); +goog.addDependency("../../../" + dir + "/core/tooltip.js", ['Blockly.Tooltip'], ['goog.dom', 'goog.dom.TagName']); +goog.addDependency("../../../" + dir + "/core/field_angle.js", ['Blockly.FieldAngle'], ['Blockly.FieldTextInput', 'goog.math', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/zoom_controls.js", ['Blockly.ZoomControls'], ['goog.dom']); +goog.addDependency("../../../" + dir + "/core/workspace.js", ['Blockly.Workspace'], ['goog.math']); +goog.addDependency("../../../" + dir + "/core/field_date.js", ['Blockly.FieldDate'], ['Blockly.Field', 'goog.date', 'goog.dom', 'goog.events', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_he', 'goog.style', 'goog.ui.DatePicker']); +goog.addDependency("../../../" + dir + "/core/generator.js", ['Blockly.Generator'], ['Blockly.Block', 'goog.asserts']); +goog.addDependency("../../../" + dir + "/core/inject.js", ['Blockly.inject'], ['Blockly.Css', 'Blockly.Options', 'Blockly.WorkspaceSvg', 'goog.dom', 'goog.ui.Component', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/connection_db.js", ['Blockly.ConnectionDB'], ['Blockly.Connection']); + diff --git a/src/blockly/core/block.js b/src/blockly/core/block.js new file mode 100644 index 0000000..2021d3f --- /dev/null +++ b/src/blockly/core/block.js @@ -0,0 +1,1364 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview The class representing one block. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Block'); + +goog.require('Blockly.Blocks'); +goog.require('Blockly.Comment'); +goog.require('Blockly.Connection'); +goog.require('Blockly.Input'); +goog.require('Blockly.Mutator'); +goog.require('Blockly.Warning'); +goog.require('Blockly.Workspace'); +goog.require('Blockly.Xml'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); +goog.require('goog.string'); + + +/** + * Class for one block. + * Not normally called directly, workspace.newBlock() is preferred. + * @param {!Blockly.Workspace} workspace The block's workspace. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise + * create a new id. + * @constructor + */ +Blockly.Block = function(workspace, prototypeName, opt_id) { + /** @type {string} */ + this.id = (opt_id && !workspace.getBlockById(opt_id)) ? + opt_id : Blockly.genUid(); + workspace.blockDB_[this.id] = this; + /** @type {Blockly.Connection} */ + this.outputConnection = null; + /** @type {Blockly.Connection} */ + this.nextConnection = null; + /** @type {Blockly.Connection} */ + this.previousConnection = null; + /** @type {!Array.} */ + this.inputList = []; + /** @type {boolean|undefined} */ + this.inputsInline = undefined; + /** @type {boolean} */ + this.disabled = false; + /** @type {string|!Function} */ + this.tooltip = ''; + /** @type {boolean} */ + this.contextMenu = true; + + /** + * @type {Blockly.Block} + * @private + */ + this.parentBlock_ = null; + + /** + * @type {!Array.} + * @private + */ + this.childBlocks_ = []; + + /** + * @type {boolean} + * @private + */ + this.deletable_ = true; + + /** + * @type {boolean} + * @private + */ + this.movable_ = true; + + /** + * @type {boolean} + * @private + */ + this.editable_ = true; + + /** + * @type {boolean} + * @private + */ + this.isShadow_ = false; + + /** + * @type {boolean} + * @private + */ + this.collapsed_ = false; + + /** @type {string|Blockly.Comment} */ + this.comment = null; + + /** + * @type {!goog.math.Coordinate} + * @private + */ + this.xy_ = new goog.math.Coordinate(0, 0); + + /** @type {!Blockly.Workspace} */ + this.workspace = workspace; + /** @type {boolean} */ + this.isInFlyout = workspace.isFlyout; + /** @type {boolean} */ + this.isInMutator = workspace.isMutator; + + /** @type {boolean} */ + this.RTL = workspace.RTL; + + // Copy the type-specific functions and data from the prototype. + if (prototypeName) { + /** @type {string} */ + this.type = prototypeName; + var prototype = Blockly.Blocks[prototypeName]; + goog.asserts.assertObject(prototype, + 'Error: "%s" is an unknown language block.', prototypeName); + goog.mixin(this, prototype); + } + + workspace.addTopBlock(this); + + // Call an initialization function, if it exists. + if (goog.isFunction(this.init)) { + this.init(); + } + // Record initial inline state. + /** @type {boolean|undefined} */ + this.inputsInlineDefault = this.inputsInline; + if (Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.Create(this)); + } + // Bind an onchange function, if it exists. + if (goog.isFunction(this.onchange)) { + this.onchangeWrapper_ = this.onchange.bind(this); + this.workspace.addChangeListener(this.onchangeWrapper_); + } +}; + +/** + * Obtain a newly created block. + * @param {!Blockly.Workspace} workspace The block's workspace. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @return {!Blockly.Block} The created block. + * @deprecated December 2015 + */ +Blockly.Block.obtain = function(workspace, prototypeName) { + console.warn('Deprecated call to Blockly.Block.obtain, ' + + 'use workspace.newBlock instead.'); + return workspace.newBlock(prototypeName); +}; + +/** + * Optional text data that round-trips beween blocks and XML. + * Has no effect. May be used by 3rd parties for meta information. + * @type {?string} + */ +Blockly.Block.prototype.data = null; + +/** + * Colour of the block in '#RRGGBB' format. + * @type {string} + * @private + */ +Blockly.Block.prototype.colour_ = '#000000'; + +/** + * Dispose of this block. + * @param {boolean} healStack If true, then try to heal any gap by connecting + * the next statement with the previous statement. Otherwise, dispose of + * all children of this block. + */ +Blockly.Block.prototype.dispose = function(healStack) { + if (!this.workspace) { + // Already deleted. + return; + } + // Terminate onchange event calls. + if (this.onchangeWrapper_) { + this.workspace.removeChangeListener(this.onchangeWrapper_); + } + this.unplug(healStack); + if (Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.Delete(this)); + } + Blockly.Events.disable(); + + try { + // This block is now at the top of the workspace. + // Remove this block from the workspace's list of top-most blocks. + if (this.workspace) { + this.workspace.removeTopBlock(this); + // Remove from block database. + delete this.workspace.blockDB_[this.id]; + this.workspace = null; + } + + // Just deleting this block from the DOM would result in a memory leak as + // well as corruption of the connection database. Therefore we must + // methodically step through the blocks and carefully disassemble them. + + // First, dispose of all my children. + for (var i = this.childBlocks_.length - 1; i >= 0; i--) { + this.childBlocks_[i].dispose(false); + } + // Then dispose of myself. + // Dispose of all inputs and their fields. + for (var i = 0, input; input = this.inputList[i]; i++) { + input.dispose(); + } + this.inputList.length = 0; + // Dispose of any remaining connections (next/previous/output). + var connections = this.getConnections_(true); + for (var i = 0; i < connections.length; i++) { + var connection = connections[i]; + if (connection.isConnected()) { + connection.disconnect(); + } + connections[i].dispose(); + } + } finally { + Blockly.Events.enable(); + } +}; + +/** + * Unplug this block from its superior block. If this block is a statement, + * optionally reconnect the block underneath with the block on top. + * @param {boolean} opt_healStack Disconnect child statement and reconnect + * stack. Defaults to false. + */ +Blockly.Block.prototype.unplug = function(opt_healStack) { + if (this.outputConnection) { + if (this.outputConnection.isConnected()) { + // Disconnect from any superior block. + this.outputConnection.disconnect(); + } + } else if (this.previousConnection) { + var previousTarget = null; + if (this.previousConnection.isConnected()) { + // Remember the connection that any next statements need to connect to. + previousTarget = this.previousConnection.targetConnection; + // Detach this block from the parent's tree. + this.previousConnection.disconnect(); + } + var nextBlock = this.getNextBlock(); + if (opt_healStack && nextBlock) { + // Disconnect the next statement. + var nextTarget = this.nextConnection.targetConnection; + nextTarget.disconnect(); + if (previousTarget && previousTarget.checkType_(nextTarget)) { + // Attach the next statement to the previous statement. + previousTarget.connect(nextTarget); + } + } + } +}; + +/** + * Returns all connections originating from this block. + * @return {!Array.} Array of connections. + * @private + */ +Blockly.Block.prototype.getConnections_ = function() { + var myConnections = []; + if (this.outputConnection) { + myConnections.push(this.outputConnection); + } + if (this.previousConnection) { + myConnections.push(this.previousConnection); + } + if (this.nextConnection) { + myConnections.push(this.nextConnection); + } + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.connection) { + myConnections.push(input.connection); + } + } + return myConnections; +}; + +/** + * Walks down a stack of blocks and finds the last next connection on the stack. + * @return {Blockly.Connection} The last next connection on the stack, or null. + * @private + */ +Blockly.Block.prototype.lastConnectionInStack_ = function() { + var nextConnection = this.nextConnection; + while (nextConnection) { + var nextBlock = nextConnection.targetBlock(); + if (!nextBlock) { + // Found a next connection with nothing on the other side. + return nextConnection; + } + nextConnection = nextBlock.nextConnection; + } + // Ran out of next connections. + return null; +}; + +/** + * Bump unconnected blocks out of alignment. Two blocks which aren't actually + * connected should not coincidentally line up on screen. + * @private + */ +Blockly.Block.prototype.bumpNeighbours_ = function() { + if (!this.workspace) { + return; // Deleted block. + } + if (Blockly.dragMode_ != Blockly.DRAG_NONE) { + return; // Don't bump blocks during a drag. + } + var rootBlock = this.getRootBlock(); + if (rootBlock.isInFlyout) { + return; // Don't move blocks around in a flyout. + } + // Loop though every connection on this block. + var myConnections = this.getConnections_(false); + for (var i = 0, connection; connection = myConnections[i]; i++) { + // Spider down from this block bumping all sub-blocks. + if (connection.isConnected() && connection.isSuperior()) { + connection.targetBlock().bumpNeighbours_(); + } + + var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS); + for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) { + // If both connections are connected, that's probably fine. But if + // either one of them is unconnected, then there could be confusion. + if (!connection.isConnected() || !otherConnection.isConnected()) { + // Only bump blocks if they are from different tree structures. + if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) { + // Always bump the inferior block. + if (connection.isSuperior()) { + otherConnection.bumpAwayFrom_(connection); + } else { + connection.bumpAwayFrom_(otherConnection); + } + } + } + } + } +}; + +/** + * Return the parent block or null if this block is at the top level. + * @return {Blockly.Block} The block that holds the current block. + */ +Blockly.Block.prototype.getParent = function() { + // Look at the DOM to see if we are nested in another block. + return this.parentBlock_; +}; + +/** + * Return the input that connects to the specified block. + * @param {!Blockly.Block} block A block connected to an input on this block. + * @return {Blockly.Input} The input that connects to the specified block. + */ +Blockly.Block.prototype.getInputWithBlock = function(block) { + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.connection && input.connection.targetBlock() == block) { + return input; + } + } + return null; +}; + +/** + * Return the parent block that surrounds the current block, or null if this + * block has no surrounding block. A parent block might just be the previous + * statement, whereas the surrounding block is an if statement, while loop, etc. + * @return {Blockly.Block} The block that surrounds the current block. + */ +Blockly.Block.prototype.getSurroundParent = function() { + var block = this; + do { + var prevBlock = block; + block = block.getParent(); + if (!block) { + // Ran off the top. + return null; + } + } while (block.getNextBlock() == prevBlock); + // This block is an enclosing parent, not just a statement in a stack. + return block; +}; + +/** + * Return the next statement block directly connected to this block. + * @return {Blockly.Block} The next statement block or null. + */ +Blockly.Block.prototype.getNextBlock = function() { + return this.nextConnection && this.nextConnection.targetBlock(); +}; + +/** + * Return the top-most block in this block's tree. + * This will return itself if this block is at the top level. + * @return {!Blockly.Block} The root block. + */ +Blockly.Block.prototype.getRootBlock = function() { + var rootBlock; + var block = this; + do { + rootBlock = block; + block = rootBlock.parentBlock_; + } while (block); + return rootBlock; +}; + +/** + * Find all the blocks that are directly nested inside this one. + * Includes value and block inputs, as well as any following statement. + * Excludes any connection on an output tab or any preceding statement. + * @return {!Array.} Array of blocks. + */ +Blockly.Block.prototype.getChildren = function() { + return this.childBlocks_; +}; + +/** + * Set parent of this block to be a new block or null. + * @param {Blockly.Block} newParent New parent block. + */ +Blockly.Block.prototype.setParent = function(newParent) { + if (newParent == this.parentBlock_) { + return; + } + if (this.parentBlock_) { + // Remove this block from the old parent's child list. + var children = this.parentBlock_.childBlocks_; + for (var child, x = 0; child = children[x]; x++) { + if (child == this) { + children.splice(x, 1); + break; + } + } + + // Disconnect from superior blocks. + if (this.previousConnection && this.previousConnection.isConnected()) { + throw 'Still connected to previous block.'; + } + if (this.outputConnection && this.outputConnection.isConnected()) { + throw 'Still connected to parent block.'; + } + this.parentBlock_ = null; + // This block hasn't actually moved on-screen, so there's no need to update + // its connection locations. + } else { + // Remove this block from the workspace's list of top-most blocks. + this.workspace.removeTopBlock(this); + } + + this.parentBlock_ = newParent; + if (newParent) { + // Add this block to the new parent's child list. + newParent.childBlocks_.push(this); + } else { + this.workspace.addTopBlock(this); + } +}; + +/** + * Find all the blocks that are directly or indirectly nested inside this one. + * Includes this block in the list. + * Includes value and block inputs, as well as any following statements. + * Excludes any connection on an output tab or any preceding statements. + * @return {!Array.} Flattened array of blocks. + */ +Blockly.Block.prototype.getDescendants = function() { + var blocks = [this]; + for (var child, x = 0; child = this.childBlocks_[x]; x++) { + blocks.push.apply(blocks, child.getDescendants()); + } + return blocks; +}; + +/** + * Get whether this block is deletable or not. + * @return {boolean} True if deletable. + */ +Blockly.Block.prototype.isDeletable = function() { + return this.deletable_ && !this.isShadow_ && + !(this.workspace && this.workspace.options.readOnly); +}; + +/** + * Set whether this block is deletable or not. + * @param {boolean} deletable True if deletable. + */ +Blockly.Block.prototype.setDeletable = function(deletable) { + this.deletable_ = deletable; +}; + +/** + * Get whether this block is movable or not. + * @return {boolean} True if movable. + */ +Blockly.Block.prototype.isMovable = function() { + return this.movable_ && !this.isShadow_ && + !(this.workspace && this.workspace.options.readOnly); +}; + +/** + * Set whether this block is movable or not. + * @param {boolean} movable True if movable. + */ +Blockly.Block.prototype.setMovable = function(movable) { + this.movable_ = movable; +}; + +/** + * Get whether this block is a shadow block or not. + * @return {boolean} True if a shadow. + */ +Blockly.Block.prototype.isShadow = function() { + return this.isShadow_; +}; + +/** + * Set whether this block is a shadow block or not. + * @param {boolean} shadow True if a shadow. + */ +Blockly.Block.prototype.setShadow = function(shadow) { + this.isShadow_ = shadow; +}; + +/** + * Get whether this block is editable or not. + * @return {boolean} True if editable. + */ +Blockly.Block.prototype.isEditable = function() { + return this.editable_ && !(this.workspace && this.workspace.options.readOnly); +}; + +/** + * Set whether this block is editable or not. + * @param {boolean} editable True if editable. + */ +Blockly.Block.prototype.setEditable = function(editable) { + this.editable_ = editable; + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + field.updateEditable(); + } + } +}; + +/** + * Set whether the connections are hidden (not tracked in a database) or not. + * Recursively walk down all child blocks (except collapsed blocks). + * @param {boolean} hidden True if connections are hidden. + */ +Blockly.Block.prototype.setConnectionsHidden = function(hidden) { + if (!hidden && this.isCollapsed()) { + if (this.outputConnection) { + this.outputConnection.setHidden(hidden); + } + if (this.previousConnection) { + this.previousConnection.setHidden(hidden); + } + if (this.nextConnection) { + this.nextConnection.setHidden(hidden); + var child = this.nextConnection.targetBlock(); + if (child) { + child.setConnectionsHidden(hidden); + } + } + } else { + var myConnections = this.getConnections_(true); + for (var i = 0, connection; connection = myConnections[i]; i++) { + connection.setHidden(hidden); + if (connection.isSuperior()) { + var child = connection.targetBlock(); + if (child) { + child.setConnectionsHidden(hidden); + } + } + } + } +}; + +/** + * Set the URL of this block's help page. + * @param {string|Function} url URL string for block help, or function that + * returns a URL. Null for no help. + */ +Blockly.Block.prototype.setHelpUrl = function(url) { + this.helpUrl = url; +}; + +/** + * Change the tooltip text for a block. + * @param {string|!Function} newTip Text for tooltip or a parent element to + * link to for its tooltip. May be a function that returns a string. + */ +Blockly.Block.prototype.setTooltip = function(newTip) { + this.tooltip = newTip; +}; + +/** + * Get the colour of a block. + * @return {string} #RRGGBB string. + */ +Blockly.Block.prototype.getColour = function() { + return this.colour_; +}; + +/** + * Change the colour of a block. + * @param {number|string} colour HSV hue value, or #RRGGBB string. + */ +Blockly.Block.prototype.setColour = function(colour) { + var hue = parseFloat(colour); + if (!isNaN(hue)) { + this.colour_ = Blockly.hueToRgb(hue); + } else if (goog.isString(colour) && colour.match(/^#[0-9a-fA-F]{6}$/)) { + this.colour_ = colour; + } else { + throw 'Invalid colour: ' + colour; + } +}; + +/** + * Returns the named field from a block. + * @param {string} name The name of the field. + * @return {Blockly.Field} Named field, or null if field does not exist. + */ +Blockly.Block.prototype.getField = function(name) { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field.name === name) { + return field; + } + } + } + return null; +}; + +/** + * Return all variables referenced by this block. + * @return {!Array.} List of variable names. + */ +Blockly.Block.prototype.getVars = function() { + var vars = []; + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldVariable) { + vars.push(field.getValue()); + } + } + } + return vars; +}; + +/** + * Notification that a variable is renaming. + * If the name matches one of this block's variables, rename it. + * @param {string} oldName Previous name of variable. + * @param {string} newName Renamed variable. + */ +Blockly.Block.prototype.renameVar = function(oldName, newName) { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldVariable && + Blockly.Names.equals(oldName, field.getValue())) { + field.setValue(newName); + } + } + } +}; + +/** + * Returns the language-neutral value from the field of a block. + * @param {string} name The name of the field. + * @return {?string} Value from the field or null if field does not exist. + */ +Blockly.Block.prototype.getFieldValue = function(name) { + var field = this.getField(name); + if (field) { + return field.getValue(); + } + return null; +}; + +/** + * Returns the language-neutral value from the field of a block. + * @param {string} name The name of the field. + * @return {?string} Value from the field or null if field does not exist. + * @deprecated December 2013 + */ +Blockly.Block.prototype.getTitleValue = function(name) { + console.warn('Deprecated call to getTitleValue, use getFieldValue instead.'); + return this.getFieldValue(name); +}; + +/** + * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE'). + * @param {string} newValue Value to be the new field. + * @param {string} name The name of the field. + */ +Blockly.Block.prototype.setFieldValue = function(newValue, name) { + var field = this.getField(name); + goog.asserts.assertObject(field, 'Field "%s" not found.', name); + field.setValue(newValue); +}; + +/** + * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE'). + * @param {string} newValue Value to be the new field. + * @param {string} name The name of the field. + * @deprecated December 2013 + */ +Blockly.Block.prototype.setTitleValue = function(newValue, name) { + console.warn('Deprecated call to setTitleValue, use setFieldValue instead.'); + this.setFieldValue(newValue, name); +}; + +/** + * Set whether this block can chain onto the bottom of another block. + * @param {boolean} newBoolean True if there can be a previous statement. + * @param {string|Array.|null|undefined} opt_check Statement type or + * list of statement types. Null/undefined if any type could be connected. + */ +Blockly.Block.prototype.setPreviousStatement = function(newBoolean, opt_check) { + if (newBoolean) { + if (opt_check === undefined) { + opt_check = null; + } + if (!this.previousConnection) { + goog.asserts.assert(!this.outputConnection, + 'Remove output connection prior to adding previous connection.'); + this.previousConnection = + this.makeConnection_(Blockly.PREVIOUS_STATEMENT); + } + this.previousConnection.setCheck(opt_check); + } else { + if (this.previousConnection) { + goog.asserts.assert(!this.previousConnection.isConnected(), + 'Must disconnect previous statement before removing connection.'); + this.previousConnection.dispose(); + this.previousConnection = null; + } + } +}; + +/** + * Set whether another block can chain onto the bottom of this block. + * @param {boolean} newBoolean True if there can be a next statement. + * @param {string|Array.|null|undefined} opt_check Statement type or + * list of statement types. Null/undefined if any type could be connected. + */ +Blockly.Block.prototype.setNextStatement = function(newBoolean, opt_check) { + if (newBoolean) { + if (opt_check === undefined) { + opt_check = null; + } + if (!this.nextConnection) { + this.nextConnection = this.makeConnection_(Blockly.NEXT_STATEMENT); + } + this.nextConnection.setCheck(opt_check); + } else { + if (this.nextConnection) { + goog.asserts.assert(!this.nextConnection.isConnected(), + 'Must disconnect next statement before removing connection.'); + this.nextConnection.dispose(); + this.nextConnection = null; + } + } +}; + +/** + * Set whether this block returns a value. + * @param {boolean} newBoolean True if there is an output. + * @param {string|Array.|null|undefined} opt_check Returned type or list + * of returned types. Null or undefined if any type could be returned + * (e.g. variable get). + */ +Blockly.Block.prototype.setOutput = function(newBoolean, opt_check) { + if (newBoolean) { + if (opt_check === undefined) { + opt_check = null; + } + if (!this.outputConnection) { + goog.asserts.assert(!this.previousConnection, + 'Remove previous connection prior to adding output connection.'); + this.outputConnection = this.makeConnection_(Blockly.OUTPUT_VALUE); + } + this.outputConnection.setCheck(opt_check); + } else { + if (this.outputConnection) { + goog.asserts.assert(!this.outputConnection.isConnected(), + 'Must disconnect output value before removing connection.'); + this.outputConnection.dispose(); + this.outputConnection = null; + } + } +}; + +/** + * Set whether value inputs are arranged horizontally or vertically. + * @param {boolean} newBoolean True if inputs are horizontal. + */ +Blockly.Block.prototype.setInputsInline = function(newBoolean) { + if (this.inputsInline != newBoolean) { + Blockly.Events.fire(new Blockly.Events.Change( + this, 'inline', null, this.inputsInline, newBoolean)); + this.inputsInline = newBoolean; + } +}; + +/** + * Get whether value inputs are arranged horizontally or vertically. + * @return {boolean} True if inputs are horizontal. + */ +Blockly.Block.prototype.getInputsInline = function() { + if (this.inputsInline != undefined) { + // Set explicitly. + return this.inputsInline; + } + // Not defined explicitly. Figure out what would look best. + for (var i = 1; i < this.inputList.length; i++) { + if (this.inputList[i - 1].type == Blockly.DUMMY_INPUT && + this.inputList[i].type == Blockly.DUMMY_INPUT) { + // Two dummy inputs in a row. Don't inline them. + return false; + } + } + for (var i = 1; i < this.inputList.length; i++) { + if (this.inputList[i - 1].type == Blockly.INPUT_VALUE && + this.inputList[i].type == Blockly.DUMMY_INPUT) { + // Dummy input after a value input. Inline them. + return true; + } + } + return false; +}; + +/** + * Set whether the block is disabled or not. + * @param {boolean} disabled True if disabled. + */ +Blockly.Block.prototype.setDisabled = function(disabled) { + if (this.disabled != disabled) { + Blockly.Events.fire(new Blockly.Events.Change( + this, 'disabled', null, this.disabled, disabled)); + this.disabled = disabled; + } +}; + +/** + * Get whether the block is disabled or not due to parents. + * The block's own disabled property is not considered. + * @return {boolean} True if disabled. + */ +Blockly.Block.prototype.getInheritedDisabled = function() { + var block = this; + while (true) { + block = block.getSurroundParent(); + if (!block) { + // Ran off the top. + return false; + } else if (block.disabled) { + return true; + } + } +}; + +/** + * Get whether the block is collapsed or not. + * @return {boolean} True if collapsed. + */ +Blockly.Block.prototype.isCollapsed = function() { + return this.collapsed_; +}; + +/** + * Set whether the block is collapsed or not. + * @param {boolean} collapsed True if collapsed. + */ +Blockly.Block.prototype.setCollapsed = function(collapsed) { + if (this.collapsed_ != collapsed) { + Blockly.Events.fire(new Blockly.Events.Change( + this, 'collapsed', null, this.collapsed_, collapsed)); + this.collapsed_ = collapsed; + } +}; + +/** + * Create a human-readable text representation of this block and any children. + * @param {number=} opt_maxLength Truncate the string to this length. + * @param {string=} opt_emptyToken The placeholder string used to denote an + * empty field. If not specified, '?' is used. + * @return {string} Text of block. + */ +Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) { + var text = []; + var emptyFieldPlaceholder = opt_emptyToken || '?'; + if (this.collapsed_) { + text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].text_); + } else { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + text.push(field.getText()); + } + if (input.connection) { + var child = input.connection.targetBlock(); + if (child) { + text.push(child.toString(undefined, opt_emptyToken)); + } else { + text.push(emptyFieldPlaceholder); + } + } + } + } + text = goog.string.trim(text.join(' ')) || '???'; + if (opt_maxLength) { + // TODO: Improve truncation so that text from this block is given priority. + // E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not "1+2+3+4+5...". + // E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...". + text = goog.string.truncate(text, opt_maxLength); + } + return text; +}; + +/** + * Shortcut for appending a value input row. + * @param {string} name Language-neutral identifier which may used to find this + * input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + */ +Blockly.Block.prototype.appendValueInput = function(name) { + return this.appendInput_(Blockly.INPUT_VALUE, name); +}; + +/** + * Shortcut for appending a statement input row. + * @param {string} name Language-neutral identifier which may used to find this + * input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + */ +Blockly.Block.prototype.appendStatementInput = function(name) { + return this.appendInput_(Blockly.NEXT_STATEMENT, name); +}; + +/** + * Shortcut for appending a dummy input row. + * @param {string=} opt_name Language-neutral identifier which may used to find + * this input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + */ +Blockly.Block.prototype.appendDummyInput = function(opt_name) { + return this.appendInput_(Blockly.DUMMY_INPUT, opt_name || ''); +}; + +/** + * Initialize this block using a cross-platform, internationalization-friendly + * JSON description. + * @param {!Object} json Structured data describing the block. + */ +Blockly.Block.prototype.jsonInit = function(json) { + // Validate inputs. + goog.asserts.assert(json['output'] == undefined || + json['previousStatement'] == undefined, + 'Must not have both an output and a previousStatement.'); + + // Set basic properties of block. + if (json['colour'] !== undefined) { + this.setColour(json['colour']); + } + + // Interpolate the message blocks. + var i = 0; + while (json['message' + i] !== undefined) { + this.interpolate_(json['message' + i], json['args' + i] || [], + json['lastDummyAlign' + i]); + i++; + } + + if (json['inputsInline'] !== undefined) { + this.setInputsInline(json['inputsInline']); + } + // Set output and previous/next connections. + if (json['output'] !== undefined) { + this.setOutput(true, json['output']); + } + if (json['previousStatement'] !== undefined) { + this.setPreviousStatement(true, json['previousStatement']); + } + if (json['nextStatement'] !== undefined) { + this.setNextStatement(true, json['nextStatement']); + } + if (json['tooltip'] !== undefined) { + this.setTooltip(json['tooltip']); + } + if (json['helpUrl'] !== undefined) { + this.setHelpUrl(json['helpUrl']); + } +}; + +/** + * Interpolate a message description onto the block. + * @param {string} message Text contains interpolation tokens (%1, %2, ...) + * that match with fields or inputs defined in the args array. + * @param {!Array} args Array of arguments to be interpolated. + * @param {=string} lastDummyAlign If a dummy input is added at the end, + * how should it be aligned? + * @private + */ +Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { + var tokens = Blockly.utils.tokenizeInterpolation(message); + // Interpolate the arguments. Build a list of elements. + var indexDup = []; + var indexCount = 0; + var elements = []; + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (typeof token == 'number') { + goog.asserts.assert(token > 0 && token <= args.length, + 'Message index "%s" out of range.', token); + goog.asserts.assert(!indexDup[token], + 'Message index "%s" duplicated.', token); + indexDup[token] = true; + indexCount++; + elements.push(args[token - 1]); + } else { + token = token.trim(); + if (token) { + elements.push(token); + } + } + } + goog.asserts.assert(indexCount == args.length, + 'Message does not reference all %s arg(s).', args.length); + // Add last dummy input if needed. + if (elements.length && (typeof elements[elements.length - 1] == 'string' || + elements[elements.length - 1]['type'].indexOf('field_') == 0)) { + var dummyInput = {type: 'input_dummy'}; + if (lastDummyAlign) { + dummyInput['align'] = lastDummyAlign; + } + elements.push(dummyInput); + } + // Lookup of alignment constants. + var alignmentLookup = { + 'LEFT': Blockly.ALIGN_LEFT, + 'RIGHT': Blockly.ALIGN_RIGHT, + 'CENTRE': Blockly.ALIGN_CENTRE + }; + // Populate block with inputs and fields. + var fieldStack = []; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (typeof element == 'string') { + fieldStack.push([element, undefined]); + } else { + var field = null; + var input = null; + do { + var altRepeat = false; + if (typeof element == 'string') { + field = new Blockly.FieldLabel(element); + } else { + switch (element['type']) { + case 'input_value': + input = this.appendValueInput(element['name']); + break; + case 'input_statement': + input = this.appendStatementInput(element['name']); + break; + case 'input_dummy': + input = this.appendDummyInput(element['name']); + break; + case 'field_label': + field = new Blockly.FieldLabel(element['text'], element['class']); + break; + case 'field_input': + field = new Blockly.FieldTextInput(element['text']); + if (typeof element['spellcheck'] == 'boolean') { + field.setSpellcheck(element['spellcheck']); + } + break; + case 'field_angle': + field = new Blockly.FieldAngle(element['angle']); + break; + case 'field_checkbox': + field = new Blockly.FieldCheckbox( + element['checked'] ? 'TRUE' : 'FALSE'); + break; + case 'field_colour': + field = new Blockly.FieldColour(element['colour']); + break; + case 'field_variable': + field = new Blockly.FieldVariable(element['variable']); + break; + case 'field_dropdown': + field = new Blockly.FieldDropdown(element['options']); + break; + case 'field_image': + field = new Blockly.FieldImage(element['src'], + element['width'], element['height'], element['alt']); + break; + case 'field_number': + field = new Blockly.FieldNumber(element['value'], + element['min'], element['max'], element['precision']); + break; + case 'field_date': + if (Blockly.FieldDate) { + field = new Blockly.FieldDate(element['date']); + break; + } + // Fall through if FieldDate is not compiled in. + default: + // Unknown field. + if (element['alt']) { + element = element['alt']; + altRepeat = true; + } + } + } + } while (altRepeat); + if (field) { + fieldStack.push([field, element['name']]); + } else if (input) { + if (element['check']) { + input.setCheck(element['check']); + } + if (element['align']) { + input.setAlign(alignmentLookup[element['align']]); + } + for (var j = 0; j < fieldStack.length; j++) { + input.appendField(fieldStack[j][0], fieldStack[j][1]); + } + fieldStack.length = 0; + } + } + } +}; + +/** + * Add a value input, statement input or local variable to this block. + * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or + * Blockly.DUMMY_INPUT. + * @param {string} name Language-neutral identifier which may used to find this + * input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + * @private + */ +Blockly.Block.prototype.appendInput_ = function(type, name) { + var connection = null; + if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT) { + connection = this.makeConnection_(type); + } + var input = new Blockly.Input(type, name, this, connection); + // Append input to list. + this.inputList.push(input); + return input; +}; + +/** + * Move a named input to a different location on this block. + * @param {string} name The name of the input to move. + * @param {?string} refName Name of input that should be after the moved input, + * or null to be the input at the end. + */ +Blockly.Block.prototype.moveInputBefore = function(name, refName) { + if (name == refName) { + return; + } + // Find both inputs. + var inputIndex = -1; + var refIndex = refName ? -1 : this.inputList.length; + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.name == name) { + inputIndex = i; + if (refIndex != -1) { + break; + } + } else if (refName && input.name == refName) { + refIndex = i; + if (inputIndex != -1) { + break; + } + } + } + goog.asserts.assert(inputIndex != -1, 'Named input "%s" not found.', name); + goog.asserts.assert(refIndex != -1, 'Reference input "%s" not found.', + refName); + this.moveNumberedInputBefore(inputIndex, refIndex); +}; + +/** + * Move a numbered input to a different location on this block. + * @param {number} inputIndex Index of the input to move. + * @param {number} refIndex Index of input that should be after the moved input. + */ +Blockly.Block.prototype.moveNumberedInputBefore = function( + inputIndex, refIndex) { + // Validate arguments. + goog.asserts.assert(inputIndex != refIndex, 'Can\'t move input to itself.'); + goog.asserts.assert(inputIndex < this.inputList.length, + 'Input index ' + inputIndex + ' out of bounds.'); + goog.asserts.assert(refIndex <= this.inputList.length, + 'Reference input ' + refIndex + ' out of bounds.'); + // Remove input. + var input = this.inputList[inputIndex]; + this.inputList.splice(inputIndex, 1); + if (inputIndex < refIndex) { + refIndex--; + } + // Reinsert input. + this.inputList.splice(refIndex, 0, input); +}; + +/** + * Remove an input from this block. + * @param {string} name The name of the input. + * @param {boolean=} opt_quiet True to prevent error if input is not present. + * @throws {goog.asserts.AssertionError} if the input is not present and + * opt_quiet is not true. + */ +Blockly.Block.prototype.removeInput = function(name, opt_quiet) { + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.name == name) { + if (input.connection && input.connection.isConnected()) { + input.connection.setShadowDom(null); + var block = input.connection.targetBlock(); + if (block.isShadow()) { + // Destroy any attached shadow block. + block.dispose(); + } else { + // Disconnect any attached normal block. + block.unplug(); + } + } + input.dispose(); + this.inputList.splice(i, 1); + return; + } + } + if (!opt_quiet) { + goog.asserts.fail('Input "%s" not found.', name); + } +}; + +/** + * Fetches the named input object. + * @param {string} name The name of the input. + * @return {Blockly.Input} The input object, or null if input does not exist. + */ +Blockly.Block.prototype.getInput = function(name) { + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.name == name) { + return input; + } + } + // This input does not exist. + return null; +}; + +/** + * Fetches the block attached to the named input. + * @param {string} name The name of the input. + * @return {Blockly.Block} The attached value block, or null if the input is + * either disconnected or if the input does not exist. + */ +Blockly.Block.prototype.getInputTargetBlock = function(name) { + var input = this.getInput(name); + return input && input.connection && input.connection.targetBlock(); +}; + +/** + * Returns the comment on this block (or '' if none). + * @return {string} Block's comment. + */ +Blockly.Block.prototype.getCommentText = function() { + return this.comment || ''; +}; + +/** + * Set this block's comment text. + * @param {?string} text The text, or null to delete. + */ +Blockly.Block.prototype.setCommentText = function(text) { + if (this.comment != text) { + Blockly.Events.fire(new Blockly.Events.Change( + this, 'comment', null, this.comment, text || '')); + this.comment = text; + } +}; + +/** + * Set this block's warning text. + * @param {?string} text The text, or null to delete. + */ +Blockly.Block.prototype.setWarningText = function(text) { + // NOP. +}; + +/** + * Give this block a mutator dialog. + * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove. + */ +Blockly.Block.prototype.setMutator = function(mutator) { + // NOP. +}; + +/** + * Return the coordinates of the top-left corner of this block relative to the + * drawing surface's origin (0,0). + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.Block.prototype.getRelativeToSurfaceXY = function() { + return this.xy_; +}; + +/** + * Move a block by a relative offset. + * @param {number} dx Horizontal offset. + * @param {number} dy Vertical offset. + */ +Blockly.Block.prototype.moveBy = function(dx, dy) { + goog.asserts.assert(!this.parentBlock_, 'Block has parent.'); + var event = new Blockly.Events.Move(this); + this.xy_.translate(dx, dy); + event.recordNew(); + Blockly.Events.fire(event); +}; + +/** + * Create a connection of the specified type. + * @param {number} type The type of the connection to create. + * @return {!Blockly.Connection} A new connection of the specified type. + * @private + */ +Blockly.Block.prototype.makeConnection_ = function(type) { + return new Blockly.Connection(this, type); +}; diff --git a/src/blockly/core/block_render_svg.js b/src/blockly/core/block_render_svg.js new file mode 100644 index 0000000..14c42b0 --- /dev/null +++ b/src/blockly/core/block_render_svg.js @@ -0,0 +1,969 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Methods for graphically rendering a block as SVG. + * @author fenichel@google.com (Rachel Fenichel) + */ + +'use strict'; + +goog.provide('Blockly.BlockSvg.render'); + +goog.require('Blockly.BlockSvg'); + + +// UI constants for rendering blocks. +/** + * Horizontal space between elements. + * @const + */ +Blockly.BlockSvg.SEP_SPACE_X = 10; +/** + * Vertical space between elements. + * @const + */ +Blockly.BlockSvg.SEP_SPACE_Y = 10; +/** + * Vertical padding around inline elements. + * @const + */ +Blockly.BlockSvg.INLINE_PADDING_Y = 5; +/** + * Minimum height of a block. + * @const + */ +Blockly.BlockSvg.MIN_BLOCK_Y = 25; +/** + * Height of horizontal puzzle tab. + * @const + */ +Blockly.BlockSvg.TAB_HEIGHT = 20; +/** + * Width of horizontal puzzle tab. + * @const + */ +Blockly.BlockSvg.TAB_WIDTH = 8; +/** + * Width of vertical tab (inc left margin). + * @const + */ +Blockly.BlockSvg.NOTCH_WIDTH = 30; +/** + * Rounded corner radius. + * @const + */ +Blockly.BlockSvg.CORNER_RADIUS = 8; +/** + * Do blocks with no previous or output connections have a 'hat' on top? + * @const + */ +Blockly.BlockSvg.START_HAT = false; +/** + * Height of the top hat. + * @const + */ +Blockly.BlockSvg.START_HAT_HEIGHT = 15; +/** + * Path of the top hat's curve. + * @const + */ +Blockly.BlockSvg.START_HAT_PATH = 'c 30,-' + + Blockly.BlockSvg.START_HAT_HEIGHT + ' 70,-' + + Blockly.BlockSvg.START_HAT_HEIGHT + ' 100,0'; +/** + * Path of the top hat's curve's highlight in LTR. + * @const + */ +Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR = + 'c 17.8,-9.2 45.3,-14.9 75,-8.7 M 100.5,0.5'; +/** + * Path of the top hat's curve's highlight in RTL. + * @const + */ +Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL = + 'm 25,-8.7 c 29.7,-6.2 57.2,-0.5 75,8.7'; +/** + * Distance from shape edge to intersect with a curved corner at 45 degrees. + * Applies to highlighting on around the inside of a curve. + * @const + */ +Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) * + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + 0.5; +/** + * Distance from shape edge to intersect with a curved corner at 45 degrees. + * Applies to highlighting on around the outside of a curve. + * @const + */ +Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) * + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) - 0.5; +/** + * SVG path for drawing next/previous notch from left to right. + * @const + */ +Blockly.BlockSvg.NOTCH_PATH_LEFT = 'l 6,4 3,0 6,-4'; +/** + * SVG path for drawing next/previous notch from left to right with + * highlighting. + * @const + */ +Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT = 'l 6,4 3,0 6,-4'; +/** + * SVG path for drawing next/previous notch from right to left. + * @const + */ +Blockly.BlockSvg.NOTCH_PATH_RIGHT = 'l -6,4 -3,0 -6,-4'; +/** + * SVG path for drawing jagged teeth at the end of collapsed blocks. + * @const + */ +Blockly.BlockSvg.JAGGED_TEETH = 'l 8,0 0,4 8,4 -16,8 8,4'; +/** + * Height of SVG path for jagged teeth at the end of collapsed blocks. + * @const + */ +Blockly.BlockSvg.JAGGED_TEETH_HEIGHT = 20; +/** + * Width of SVG path for jagged teeth at the end of collapsed blocks. + * @const + */ +Blockly.BlockSvg.JAGGED_TEETH_WIDTH = 15; +/** + * SVG path for drawing a horizontal puzzle tab from top to bottom. + * @const + */ +Blockly.BlockSvg.TAB_PATH_DOWN = 'v 5 c 0,10 -' + Blockly.BlockSvg.TAB_WIDTH + + ',-8 -' + Blockly.BlockSvg.TAB_WIDTH + ',7.5 s ' + + Blockly.BlockSvg.TAB_WIDTH + ',-2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',7.5'; +/** + * SVG path for drawing a horizontal puzzle tab from top to bottom with + * highlighting from the upper-right. + * @const + */ +Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL = 'v 6.5 m -' + + (Blockly.BlockSvg.TAB_WIDTH * 0.97) + ',3 q -' + + (Blockly.BlockSvg.TAB_WIDTH * 0.05) + ',10 ' + + (Blockly.BlockSvg.TAB_WIDTH * 0.3) + ',9.5 m ' + + (Blockly.BlockSvg.TAB_WIDTH * 0.67) + ',-1.9 v 1.4'; + +/** + * SVG start point for drawing the top-left corner. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER_START = + 'm 0,' + Blockly.BlockSvg.CORNER_RADIUS; +/** + * SVG start point for drawing the top-left corner's highlight in RTL. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL = + 'm ' + Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' + + Blockly.BlockSvg.DISTANCE_45_INSIDE; +/** + * SVG start point for drawing the top-left corner's highlight in LTR. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR = + 'm 0.5,' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5); +/** + * SVG path for drawing the rounded top-left corner. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER = + 'A ' + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' + + Blockly.BlockSvg.CORNER_RADIUS + ',0'; +/** + * SVG path for drawing the highlight on the rounded top-left corner. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT = + 'A ' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' + + Blockly.BlockSvg.CORNER_RADIUS + ',0.5'; +/** + * SVG path for drawing the top-left corner of a statement input. + * Includes the top notch, a horizontal space, and the rounded inside corner. + * @const + */ +Blockly.BlockSvg.INNER_TOP_LEFT_CORNER = + Blockly.BlockSvg.NOTCH_PATH_RIGHT + ' h -' + + (Blockly.BlockSvg.NOTCH_WIDTH - 15 - Blockly.BlockSvg.CORNER_RADIUS) + + ' a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 -' + + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS; +/** + * SVG path for drawing the bottom-left corner of a statement input. + * Includes the rounded inside corner. + * @const + */ +Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER = + 'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' + + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS; +/** + * SVG path for drawing highlight on the top-left corner of a statement + * input in RTL. + * @const + */ +Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL = + 'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' + + (-Blockly.BlockSvg.DISTANCE_45_OUTSIDE - 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS - + Blockly.BlockSvg.DISTANCE_45_OUTSIDE); +/** + * SVG path for drawing highlight on the bottom-left corner of a statement + * input in RTL. + * @const + */ +Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL = + 'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' + + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS + 0.5); +/** + * SVG path for drawing highlight on the bottom-left corner of a statement + * input in LTR. + * @const + */ +Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR = + 'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' + + (Blockly.BlockSvg.CORNER_RADIUS - + Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' + + (Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 0.5); + +/** + * Render the block. + * Lays out and reflows a block based on its contents and settings. + * @param {boolean=} opt_bubble If false, just render this block. + * If true, also render block's parent, grandparent, etc. Defaults to true. + */ +Blockly.BlockSvg.prototype.render = function(opt_bubble) { + Blockly.Field.startCache(); + this.rendered = true; + + var cursorX = Blockly.BlockSvg.SEP_SPACE_X; + if (this.RTL) { + cursorX = -cursorX; + } + // Move the icons into position. + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + cursorX = icons[i].renderIcon(cursorX); + } + cursorX += this.RTL ? + Blockly.BlockSvg.SEP_SPACE_X : -Blockly.BlockSvg.SEP_SPACE_X; + // If there are no icons, cursorX will be 0, otherwise it will be the + // width that the first label needs to move over by. + + var inputRows = this.renderCompute_(cursorX); + this.renderDraw_(cursorX, inputRows); + this.renderMoveConnections_(); + + if (opt_bubble !== false) { + // Render all blocks above this one (propagate a reflow). + var parentBlock = this.getParent(); + if (parentBlock) { + parentBlock.render(true); + } else { + // Top-most block. Fire an event to allow scrollbars to resize. + this.workspace.resizeContents(); + } + } + Blockly.Field.stopCache(); +}; + +/** + * Render a list of fields starting at the specified location. + * @param {!Array.} fieldList List of fields. + * @param {number} cursorX X-coordinate to start the fields. + * @param {number} cursorY Y-coordinate to start the fields. + * @return {number} X-coordinate of the end of the field row (plus a gap). + * @private + */ +Blockly.BlockSvg.prototype.renderFields_ = + function(fieldList, cursorX, cursorY) { + /* eslint-disable indent */ + cursorY += Blockly.BlockSvg.INLINE_PADDING_Y; + if (this.RTL) { + cursorX = -cursorX; + } + for (var t = 0, field; field = fieldList[t]; t++) { + var root = field.getSvgRoot(); + if (!root) { + continue; + } + if (this.RTL) { + cursorX -= field.renderSep + field.renderWidth; + root.setAttribute('transform', + 'translate(' + cursorX + ',' + cursorY + ')'); + if (field.renderWidth) { + cursorX -= Blockly.BlockSvg.SEP_SPACE_X; + } + } else { + root.setAttribute('transform', + 'translate(' + (cursorX + field.renderSep) + ',' + cursorY + ')'); + if (field.renderWidth) { + cursorX += field.renderSep + field.renderWidth + + Blockly.BlockSvg.SEP_SPACE_X; + } + } + } + return this.RTL ? -cursorX : cursorX; +}; /* eslint-enable indent */ + +/** + * Computes the height and widths for each row and field. + * @param {number} iconWidth Offset of first row due to icons. + * @return {!Array.>} 2D array of objects, each containing + * position information. + * @private + */ +Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) { + var inputList = this.inputList; + var inputRows = []; + inputRows.rightEdge = iconWidth + Blockly.BlockSvg.SEP_SPACE_X * 2; + if (this.previousConnection || this.nextConnection) { + inputRows.rightEdge = Math.max(inputRows.rightEdge, + Blockly.BlockSvg.NOTCH_WIDTH + Blockly.BlockSvg.SEP_SPACE_X); + } + var fieldValueWidth = 0; // Width of longest external value field. + var fieldStatementWidth = 0; // Width of longest statement field. + var hasValue = false; + var hasStatement = false; + var hasDummy = false; + var lastType = undefined; + var isInline = this.getInputsInline() && !this.isCollapsed(); + for (var i = 0, input; input = inputList[i]; i++) { + if (!input.isVisible()) { + continue; + } + var row; + if (!isInline || !lastType || + lastType == Blockly.NEXT_STATEMENT || + input.type == Blockly.NEXT_STATEMENT) { + // Create new row. + lastType = input.type; + row = []; + if (isInline && input.type != Blockly.NEXT_STATEMENT) { + row.type = Blockly.BlockSvg.INLINE; + } else { + row.type = input.type; + } + row.height = 0; + inputRows.push(row); + } else { + row = inputRows[inputRows.length - 1]; + } + row.push(input); + + // Compute minimum input size. + input.renderHeight = Blockly.BlockSvg.MIN_BLOCK_Y; + // The width is currently only needed for inline value inputs. + if (isInline && input.type == Blockly.INPUT_VALUE) { + input.renderWidth = Blockly.BlockSvg.TAB_WIDTH + + Blockly.BlockSvg.SEP_SPACE_X * 1.25; + } else { + input.renderWidth = 0; + } + // Expand input size if there is a connection. + if (input.connection && input.connection.isConnected()) { + var linkedBlock = input.connection.targetBlock(); + var bBox = linkedBlock.getHeightWidth(); + input.renderHeight = Math.max(input.renderHeight, bBox.height); + input.renderWidth = Math.max(input.renderWidth, bBox.width); + } + // Blocks have a one pixel shadow that should sometimes overhang. + if (!isInline && i == inputList.length - 1) { + // Last value input should overhang. + input.renderHeight--; + } else if (!isInline && input.type == Blockly.INPUT_VALUE && + inputList[i + 1] && inputList[i + 1].type == Blockly.NEXT_STATEMENT) { + // Value input above statement input should overhang. + input.renderHeight--; + } + + row.height = Math.max(row.height, input.renderHeight); + input.fieldWidth = 0; + if (inputRows.length == 1) { + // The first row gets shifted to accommodate any icons. + input.fieldWidth += this.RTL ? -iconWidth : iconWidth; + } + var previousFieldEditable = false; + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (j != 0) { + input.fieldWidth += Blockly.BlockSvg.SEP_SPACE_X; + } + // Get the dimensions of the field. + var fieldSize = field.getSize(); + field.renderWidth = fieldSize.width; + field.renderSep = (previousFieldEditable && field.EDITABLE) ? + Blockly.BlockSvg.SEP_SPACE_X : 0; + input.fieldWidth += field.renderWidth + field.renderSep; + row.height = Math.max(row.height, fieldSize.height); + previousFieldEditable = field.EDITABLE; + } + + if (row.type != Blockly.BlockSvg.INLINE) { + if (row.type == Blockly.NEXT_STATEMENT) { + hasStatement = true; + fieldStatementWidth = Math.max(fieldStatementWidth, input.fieldWidth); + } else { + if (row.type == Blockly.INPUT_VALUE) { + hasValue = true; + } else if (row.type == Blockly.DUMMY_INPUT) { + hasDummy = true; + } + fieldValueWidth = Math.max(fieldValueWidth, input.fieldWidth); + } + } + } + + // Make inline rows a bit thicker in order to enclose the values. + for (var y = 0, row; row = inputRows[y]; y++) { + row.thicker = false; + if (row.type == Blockly.BlockSvg.INLINE) { + for (var z = 0, input; input = row[z]; z++) { + if (input.type == Blockly.INPUT_VALUE) { + row.height += 2 * Blockly.BlockSvg.INLINE_PADDING_Y; + row.thicker = true; + break; + } + } + } + } + + // Compute the statement edge. + // This is the width of a block where statements are nested. + inputRows.statementEdge = 2 * Blockly.BlockSvg.SEP_SPACE_X + + fieldStatementWidth; + // Compute the preferred right edge. Inline blocks may extend beyond. + // This is the width of the block where external inputs connect. + if (hasStatement) { + inputRows.rightEdge = Math.max(inputRows.rightEdge, + inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH); + } + if (hasValue) { + inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth + + Blockly.BlockSvg.SEP_SPACE_X * 2 + Blockly.BlockSvg.TAB_WIDTH); + } else if (hasDummy) { + inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth + + Blockly.BlockSvg.SEP_SPACE_X * 2); + } + + inputRows.hasValue = hasValue; + inputRows.hasStatement = hasStatement; + inputRows.hasDummy = hasDummy; + return inputRows; +}; + + +/** + * Draw the path of the block. + * Move the fields to the correct locations. + * @param {number} iconWidth Offset of first row due to icons. + * @param {!Array.>} inputRows 2D array of objects, each + * containing position information. + * @private + */ +Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) { + this.startHat_ = false; + // Reset the height to zero and let the rendering process add in + // portions of the block height as it goes. (e.g. hats, inputs, etc.) + this.height = 0; + // Should the top and bottom left corners be rounded or square? + if (this.outputConnection) { + this.squareTopLeftCorner_ = true; + this.squareBottomLeftCorner_ = true; + } else { + this.squareTopLeftCorner_ = false; + this.squareBottomLeftCorner_ = false; + // If this block is in the middle of a stack, square the corners. + if (this.previousConnection) { + var prevBlock = this.previousConnection.targetBlock(); + if (prevBlock && prevBlock.getNextBlock() == this) { + this.squareTopLeftCorner_ = true; + } + } else if (Blockly.BlockSvg.START_HAT) { + // No output or previous connection. + this.squareTopLeftCorner_ = true; + this.startHat_ = true; + this.height += Blockly.BlockSvg.START_HAT_HEIGHT; + inputRows.rightEdge = Math.max(inputRows.rightEdge, 100); + } + var nextBlock = this.getNextBlock(); + if (nextBlock) { + this.squareBottomLeftCorner_ = true; + } + } + + // Assemble the block's path. + var steps = []; + var inlineSteps = []; + // The highlighting applies to edges facing the upper-left corner. + // Since highlighting is a two-pixel wide border, it would normally overhang + // the edge of the block by a pixel. So undersize all measurements by a pixel. + var highlightSteps = []; + var highlightInlineSteps = []; + + this.renderDrawTop_(steps, highlightSteps, inputRows.rightEdge); + var cursorY = this.renderDrawRight_(steps, highlightSteps, inlineSteps, + highlightInlineSteps, inputRows, iconWidth); + this.renderDrawBottom_(steps, highlightSteps, cursorY); + this.renderDrawLeft_(steps, highlightSteps); + + var pathString = steps.join(' ') + '\n' + inlineSteps.join(' '); + this.svgPath_.setAttribute('d', pathString); + this.svgPathDark_.setAttribute('d', pathString); + pathString = highlightSteps.join(' ') + '\n' + highlightInlineSteps.join(' '); + this.svgPathLight_.setAttribute('d', pathString); + if (this.RTL) { + // Mirror the block's path. + this.svgPath_.setAttribute('transform', 'scale(-1 1)'); + this.svgPathLight_.setAttribute('transform', 'scale(-1 1)'); + this.svgPathDark_.setAttribute('transform', 'translate(1,1) scale(-1 1)'); + } +}; + +/** + * Update all of the connections on this block with the new locations calculated + * in renderCompute. Also move all of the connected blocks based on the new + * connection locations. + * @private + */ +Blockly.BlockSvg.prototype.renderMoveConnections_ = function() { + var blockTL = this.getRelativeToSurfaceXY(); + // Don't tighten previous or output connecitons because they are inferior + // connections. + if (this.previousConnection) { + this.previousConnection.moveToOffset(blockTL); + } + if (this.outputConnection) { + this.outputConnection.moveToOffset(blockTL); + } + + for (var i = 0; i < this.inputList.length; i++) { + var conn = this.inputList[i].connection; + if (conn) { + conn.moveToOffset(blockTL); + if (conn.isConnected()) { + conn.tighten_(); + } + } + } + + if (this.nextConnection) { + this.nextConnection.moveToOffset(blockTL); + if (this.nextConnection.isConnected()) { + this.nextConnection.tighten_(); + } + } + +}; + +/** + * Render the top edge of the block. + * @param {!Array.} steps Path of block outline. + * @param {!Array.} highlightSteps Path of block highlights. + * @param {number} rightEdge Minimum width of block. + * @private + */ +Blockly.BlockSvg.prototype.renderDrawTop_ = + function(steps, highlightSteps, rightEdge) { + /* eslint-disable indent */ + // Position the cursor at the top-left starting point. + if (this.squareTopLeftCorner_) { + steps.push('m 0,0'); + highlightSteps.push('m 0.5,0.5'); + if (this.startHat_) { + steps.push(Blockly.BlockSvg.START_HAT_PATH); + highlightSteps.push(this.RTL ? + Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL : + Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR); + } + } else { + steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_START); + highlightSteps.push(this.RTL ? + Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL : + Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR); + // Top-left rounded corner. + steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER); + highlightSteps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT); + } + + // Top edge. + if (this.previousConnection) { + steps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15); + highlightSteps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15); + steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT); + highlightSteps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT); + + var connectionX = (this.RTL ? + -Blockly.BlockSvg.NOTCH_WIDTH : Blockly.BlockSvg.NOTCH_WIDTH); + this.previousConnection.setOffsetInBlock(connectionX, 0); + } + steps.push('H', rightEdge); + highlightSteps.push('H', rightEdge - 0.5); + this.width = rightEdge; +}; /* eslint-enable indent */ + +/** + * Render the right edge of the block. + * @param {!Array.} steps Path of block outline. + * @param {!Array.} highlightSteps Path of block highlights. + * @param {!Array.} inlineSteps Inline block outlines. + * @param {!Array.} highlightInlineSteps Inline block highlights. + * @param {!Array.>} inputRows 2D array of objects, each + * containing position information. + * @param {number} iconWidth Offset of first row due to icons. + * @return {number} Height of block. + * @private + */ +Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, + inlineSteps, highlightInlineSteps, inputRows, iconWidth) { + var cursorX; + var cursorY = 0; + var connectionX, connectionY; + for (var y = 0, row; row = inputRows[y]; y++) { + cursorX = Blockly.BlockSvg.SEP_SPACE_X; + if (y == 0) { + cursorX += this.RTL ? -iconWidth : iconWidth; + } + highlightSteps.push('M', (inputRows.rightEdge - 0.5) + ',' + + (cursorY + 0.5)); + if (this.isCollapsed()) { + // Jagged right edge. + var input = row[0]; + var fieldX = cursorX; + var fieldY = cursorY; + this.renderFields_(input.fieldRow, fieldX, fieldY); + steps.push(Blockly.BlockSvg.JAGGED_TEETH); + highlightSteps.push('h 8'); + var remainder = row.height - Blockly.BlockSvg.JAGGED_TEETH_HEIGHT; + steps.push('v', remainder); + if (this.RTL) { + highlightSteps.push('v 3.9 l 7.2,3.4 m -14.5,8.9 l 7.3,3.5'); + highlightSteps.push('v', remainder - 0.7); + } + this.width += Blockly.BlockSvg.JAGGED_TEETH_WIDTH; + } else if (row.type == Blockly.BlockSvg.INLINE) { + // Inline inputs. + for (var x = 0, input; input = row[x]; x++) { + var fieldX = cursorX; + var fieldY = cursorY; + if (row.thicker) { + // Lower the field slightly. + fieldY += Blockly.BlockSvg.INLINE_PADDING_Y; + } + // TODO: Align inline field rows (left/right/centre). + cursorX = this.renderFields_(input.fieldRow, fieldX, fieldY); + if (input.type != Blockly.DUMMY_INPUT) { + cursorX += input.renderWidth + Blockly.BlockSvg.SEP_SPACE_X; + } + if (input.type == Blockly.INPUT_VALUE) { + inlineSteps.push('M', (cursorX - Blockly.BlockSvg.SEP_SPACE_X) + + ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y)); + inlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 - + input.renderWidth); + inlineSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN); + inlineSteps.push('v', input.renderHeight + 1 - + Blockly.BlockSvg.TAB_HEIGHT); + inlineSteps.push('h', input.renderWidth + 2 - + Blockly.BlockSvg.TAB_WIDTH); + inlineSteps.push('z'); + if (this.RTL) { + // Highlight right edge, around back of tab, and bottom. + highlightInlineSteps.push('M', + (cursorX - Blockly.BlockSvg.SEP_SPACE_X - 2.5 + + Blockly.BlockSvg.TAB_WIDTH - input.renderWidth) + ',' + + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5)); + highlightInlineSteps.push( + Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL); + highlightInlineSteps.push('v', + input.renderHeight - Blockly.BlockSvg.TAB_HEIGHT + 2.5); + highlightInlineSteps.push('h', + input.renderWidth - Blockly.BlockSvg.TAB_WIDTH + 2); + } else { + // Highlight right edge, bottom. + highlightInlineSteps.push('M', + (cursorX - Blockly.BlockSvg.SEP_SPACE_X + 0.5) + ',' + + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5)); + highlightInlineSteps.push('v', input.renderHeight + 1); + highlightInlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 - + input.renderWidth); + // Short highlight glint at bottom of tab. + highlightInlineSteps.push('M', + (cursorX - input.renderWidth - Blockly.BlockSvg.SEP_SPACE_X + + 0.9) + ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + + Blockly.BlockSvg.TAB_HEIGHT - 0.7)); + highlightInlineSteps.push('l', + (Blockly.BlockSvg.TAB_WIDTH * 0.46) + ',-2.1'); + } + // Create inline input connection. + if (this.RTL) { + connectionX = -cursorX - + Blockly.BlockSvg.TAB_WIDTH + Blockly.BlockSvg.SEP_SPACE_X + + input.renderWidth + 1; + } else { + connectionX = cursorX + + Blockly.BlockSvg.TAB_WIDTH - Blockly.BlockSvg.SEP_SPACE_X - + input.renderWidth - 1; + } + connectionY = cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 1; + input.connection.setOffsetInBlock(connectionX, connectionY); + } + } + + cursorX = Math.max(cursorX, inputRows.rightEdge); + this.width = Math.max(this.width, cursorX); + steps.push('H', cursorX); + highlightSteps.push('H', cursorX - 0.5); + steps.push('v', row.height); + if (this.RTL) { + highlightSteps.push('v', row.height - 1); + } + } else if (row.type == Blockly.INPUT_VALUE) { + // External input. + var input = row[0]; + var fieldX = cursorX; + var fieldY = cursorY; + if (input.align != Blockly.ALIGN_LEFT) { + var fieldRightX = inputRows.rightEdge - input.fieldWidth - + Blockly.BlockSvg.TAB_WIDTH - 2 * Blockly.BlockSvg.SEP_SPACE_X; + if (input.align == Blockly.ALIGN_RIGHT) { + fieldX += fieldRightX; + } else if (input.align == Blockly.ALIGN_CENTRE) { + fieldX += fieldRightX / 2; + } + } + this.renderFields_(input.fieldRow, fieldX, fieldY); + steps.push(Blockly.BlockSvg.TAB_PATH_DOWN); + var v = row.height - Blockly.BlockSvg.TAB_HEIGHT; + steps.push('v', v); + if (this.RTL) { + // Highlight around back of tab. + highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL); + highlightSteps.push('v', v + 0.5); + } else { + // Short highlight glint at bottom of tab. + highlightSteps.push('M', (inputRows.rightEdge - 5) + ',' + + (cursorY + Blockly.BlockSvg.TAB_HEIGHT - 0.7)); + highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * 0.46) + + ',-2.1'); + } + // Create external input connection. + connectionX = this.RTL ? -inputRows.rightEdge - 1 : + inputRows.rightEdge + 1; + input.connection.setOffsetInBlock(connectionX, cursorY); + if (input.connection.isConnected()) { + this.width = Math.max(this.width, inputRows.rightEdge + + input.connection.targetBlock().getHeightWidth().width - + Blockly.BlockSvg.TAB_WIDTH + 1); + } + } else if (row.type == Blockly.DUMMY_INPUT) { + // External naked field. + var input = row[0]; + var fieldX = cursorX; + var fieldY = cursorY; + if (input.align != Blockly.ALIGN_LEFT) { + var fieldRightX = inputRows.rightEdge - input.fieldWidth - + 2 * Blockly.BlockSvg.SEP_SPACE_X; + if (inputRows.hasValue) { + fieldRightX -= Blockly.BlockSvg.TAB_WIDTH; + } + if (input.align == Blockly.ALIGN_RIGHT) { + fieldX += fieldRightX; + } else if (input.align == Blockly.ALIGN_CENTRE) { + fieldX += fieldRightX / 2; + } + } + this.renderFields_(input.fieldRow, fieldX, fieldY); + steps.push('v', row.height); + if (this.RTL) { + highlightSteps.push('v', row.height - 1); + } + } else if (row.type == Blockly.NEXT_STATEMENT) { + // Nested statement. + var input = row[0]; + if (y == 0) { + // If the first input is a statement stack, add a small row on top. + steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y); + if (this.RTL) { + highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1); + } + cursorY += Blockly.BlockSvg.SEP_SPACE_Y; + } + var fieldX = cursorX; + var fieldY = cursorY; + if (input.align != Blockly.ALIGN_LEFT) { + var fieldRightX = inputRows.statementEdge - input.fieldWidth - + 2 * Blockly.BlockSvg.SEP_SPACE_X; + if (input.align == Blockly.ALIGN_RIGHT) { + fieldX += fieldRightX; + } else if (input.align == Blockly.ALIGN_CENTRE) { + fieldX += fieldRightX / 2; + } + } + this.renderFields_(input.fieldRow, fieldX, fieldY); + cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH; + steps.push('H', cursorX); + steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER); + steps.push('v', row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS); + steps.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER); + steps.push('H', inputRows.rightEdge); + if (this.RTL) { + highlightSteps.push('M', + (cursorX - Blockly.BlockSvg.NOTCH_WIDTH + + Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + + ',' + (cursorY + Blockly.BlockSvg.DISTANCE_45_OUTSIDE)); + highlightSteps.push( + Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL); + highlightSteps.push('v', + row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS); + highlightSteps.push( + Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL); + highlightSteps.push('H', inputRows.rightEdge - 0.5); + } else { + highlightSteps.push('M', + (cursorX - Blockly.BlockSvg.NOTCH_WIDTH + + Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' + + (cursorY + row.height - Blockly.BlockSvg.DISTANCE_45_OUTSIDE)); + highlightSteps.push( + Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR); + highlightSteps.push('H', inputRows.rightEdge - 0.5); + } + // Create statement connection. + connectionX = this.RTL ? -cursorX : cursorX + 1; + input.connection.setOffsetInBlock(connectionX, cursorY + 1); + + if (input.connection.isConnected()) { + this.width = Math.max(this.width, inputRows.statementEdge + + input.connection.targetBlock().getHeightWidth().width); + } + if (y == inputRows.length - 1 || + inputRows[y + 1].type == Blockly.NEXT_STATEMENT) { + // If the final input is a statement stack, add a small row underneath. + // Consecutive statement stacks are also separated by a small divider. + steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y); + if (this.RTL) { + highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1); + } + cursorY += Blockly.BlockSvg.SEP_SPACE_Y; + } + } + cursorY += row.height; + } + if (!inputRows.length) { + cursorY = Blockly.BlockSvg.MIN_BLOCK_Y; + steps.push('V', cursorY); + if (this.RTL) { + highlightSteps.push('V', cursorY - 1); + } + } + return cursorY; +}; + +/** + * Render the bottom edge of the block. + * @param {!Array.} steps Path of block outline. + * @param {!Array.} highlightSteps Path of block highlights. + * @param {number} cursorY Height of block. + * @private + */ +Blockly.BlockSvg.prototype.renderDrawBottom_ = + function(steps, highlightSteps, cursorY) { + /* eslint-disable indent */ + this.height += cursorY + 1; // Add one for the shadow. + if (this.nextConnection) { + steps.push('H', (Blockly.BlockSvg.NOTCH_WIDTH + (this.RTL ? 0.5 : - 0.5)) + + ' ' + Blockly.BlockSvg.NOTCH_PATH_RIGHT); + // Create next block connection. + var connectionX; + if (this.RTL) { + connectionX = -Blockly.BlockSvg.NOTCH_WIDTH; + } else { + connectionX = Blockly.BlockSvg.NOTCH_WIDTH; + } + this.nextConnection.setOffsetInBlock(connectionX, cursorY + 1); + this.height += 4; // Height of tab. + } + + // Should the bottom-left corner be rounded or square? + if (this.squareBottomLeftCorner_) { + steps.push('H 0'); + if (!this.RTL) { + highlightSteps.push('M', '0.5,' + (cursorY - 0.5)); + } + } else { + steps.push('H', Blockly.BlockSvg.CORNER_RADIUS); + steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 -' + + Blockly.BlockSvg.CORNER_RADIUS + ',-' + + Blockly.BlockSvg.CORNER_RADIUS); + if (!this.RTL) { + highlightSteps.push('M', Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' + + (cursorY - Blockly.BlockSvg.DISTANCE_45_INSIDE)); + highlightSteps.push('A', (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' + + '0.5,' + (cursorY - Blockly.BlockSvg.CORNER_RADIUS)); + } + } +}; /* eslint-enable indent */ + +/** + * Render the left edge of the block. + * @param {!Array.} steps Path of block outline. + * @param {!Array.} highlightSteps Path of block highlights. + * @private + */ +Blockly.BlockSvg.prototype.renderDrawLeft_ = function(steps, highlightSteps) { + if (this.outputConnection) { + // Create output connection. + this.outputConnection.setOffsetInBlock(0, 0); + steps.push('V', Blockly.BlockSvg.TAB_HEIGHT); + steps.push('c 0,-10 -' + Blockly.BlockSvg.TAB_WIDTH + ',8 -' + + Blockly.BlockSvg.TAB_WIDTH + ',-7.5 s ' + Blockly.BlockSvg.TAB_WIDTH + + ',2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',-7.5'); + if (this.RTL) { + highlightSteps.push('M', (Blockly.BlockSvg.TAB_WIDTH * -0.25) + ',8.4'); + highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * -0.45) + ',-2.1'); + } else { + highlightSteps.push('V', Blockly.BlockSvg.TAB_HEIGHT - 1.5); + highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * -0.92) + + ',-0.5 q ' + (Blockly.BlockSvg.TAB_WIDTH * -0.19) + + ',-5.5 0,-11'); + highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * 0.92) + + ',1 V 0.5 H 1'); + } + this.width += Blockly.BlockSvg.TAB_WIDTH; + } else if (!this.RTL) { + if (this.squareTopLeftCorner_) { + // Statement block in a stack. + highlightSteps.push('V', 0.5); + } else { + highlightSteps.push('V', Blockly.BlockSvg.CORNER_RADIUS); + } + } + steps.push('z'); +}; diff --git a/src/blockly/core/block_svg.js b/src/blockly/core/block_svg.js new file mode 100644 index 0000000..4d4a729 --- /dev/null +++ b/src/blockly/core/block_svg.js @@ -0,0 +1,1629 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Methods for graphically rendering a block as SVG. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.BlockSvg'); + +goog.require('Blockly.Block'); +goog.require('Blockly.ContextMenu'); +goog.require('Blockly.RenderedConnection'); +goog.require('goog.Timer'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.math.Coordinate'); +goog.require('goog.userAgent'); + + +/** + * Class for a block's SVG representation. + * Not normally called directly, workspace.newBlock() is preferred. + * @param {!Blockly.Workspace} workspace The block's workspace. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise + * create a new id. + * @extends {Blockly.Block} + * @constructor + */ +Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { + // Create core elements for the block. + /** + * @type {SVGElement} + * @private + */ + this.svgGroup_ = Blockly.createSvgElement('g', {}, null); + + /** + * @type {SVGElement} + * @private + */ + this.svgPathDark_ = Blockly.createSvgElement('path', + {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'}, + this.svgGroup_); + + /** + * @type {SVGElement} + * @private + */ + this.svgPath_ = Blockly.createSvgElement('path', {'class': 'blocklyPath'}, + this.svgGroup_); + + /** + * @type {SVGElement} + * @private + */ + this.svgPathLight_ = Blockly.createSvgElement('path', + {'class': 'blocklyPathLight'}, this.svgGroup_); + this.svgPath_.tooltip = this; + + /** @type {boolean} */ + this.rendered = false; + + Blockly.Tooltip.bindMouseEvents(this.svgPath_); + Blockly.BlockSvg.superClass_.constructor.call(this, + workspace, prototypeName, opt_id); +}; +goog.inherits(Blockly.BlockSvg, Blockly.Block); + +/** + * Height of this block, not including any statement blocks above or below. + */ +Blockly.BlockSvg.prototype.height = 0; +/** + * Width of this block, including any connected value blocks. + */ +Blockly.BlockSvg.prototype.width = 0; + +/** + * Original location of block being dragged. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.BlockSvg.prototype.dragStartXY_ = null; + +/** + * Constant for identifying rows that are to be rendered inline. + * Don't collide with Blockly.INPUT_VALUE and friends. + * @const + */ +Blockly.BlockSvg.INLINE = -1; + +/** + * Create and initialize the SVG representation of the block. + * May be called more than once. + */ +Blockly.BlockSvg.prototype.initSvg = function() { + goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.'); + for (var i = 0, input; input = this.inputList[i]; i++) { + input.init(); + } + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].createIcon(); + } + this.updateColour(); + this.updateMovable(); + if (!this.workspace.options.readOnly && !this.eventsInit_) { + Blockly.bindEvent_(this.getSvgRoot(), 'mousedown', this, + this.onMouseDown_); + var thisBlock = this; + Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null, + function(e) {Blockly.longStart_(e, thisBlock);}); + } + this.eventsInit_ = true; + + if (!this.getSvgRoot().parentNode) { + this.workspace.getCanvas().appendChild(this.getSvgRoot()); + } +}; + +/** + * Select this block. Highlight it visually. + */ +Blockly.BlockSvg.prototype.select = function() { + if (this.isShadow() && this.getParent()) { + // Shadow blocks should not be selected. + this.getParent().select(); + return; + } + if (Blockly.selected == this) { + return; + } + var oldId = null; + if (Blockly.selected) { + oldId = Blockly.selected.id; + // Unselect any previously selected block. + Blockly.Events.disable(); + try { + Blockly.selected.unselect(); + } finally { + Blockly.Events.enable(); + } + } + var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id); + event.workspaceId = this.workspace.id; + Blockly.Events.fire(event); + Blockly.selected = this; + this.addSelect(); +}; + +/** + * Unselect this block. Remove its highlighting. + */ +Blockly.BlockSvg.prototype.unselect = function() { + if (Blockly.selected != this) { + return; + } + var event = new Blockly.Events.Ui(null, 'selected', this.id, null); + event.workspaceId = this.workspace.id; + Blockly.Events.fire(event); + Blockly.selected = null; + this.removeSelect(); +}; + +/** + * Block's mutator icon (if any). + * @type {Blockly.Mutator} + */ +Blockly.BlockSvg.prototype.mutator = null; + +/** + * Block's comment icon (if any). + * @type {Blockly.Comment} + */ +Blockly.BlockSvg.prototype.comment = null; + +/** + * Block's warning icon (if any). + * @type {Blockly.Warning} + */ +Blockly.BlockSvg.prototype.warning = null; + +/** + * Returns a list of mutator, comment, and warning icons. + * @return {!Array} List of icons. + */ +Blockly.BlockSvg.prototype.getIcons = function() { + var icons = []; + if (this.mutator) { + icons.push(this.mutator); + } + if (this.comment) { + icons.push(this.comment); + } + if (this.warning) { + icons.push(this.warning); + } + return icons; +}; + +/** + * Wrapper function called when a mouseUp occurs during a drag operation. + * @type {Array.} + * @private + */ +Blockly.BlockSvg.onMouseUpWrapper_ = null; + +/** + * Wrapper function called when a mouseMove occurs during a drag operation. + * @type {Array.} + * @private + */ +Blockly.BlockSvg.onMouseMoveWrapper_ = null; + +/** + * Stop binding to the global mouseup and mousemove events. + * @package + */ +Blockly.BlockSvg.terminateDrag = function() { + Blockly.BlockSvg.disconnectUiStop_(); + if (Blockly.BlockSvg.onMouseUpWrapper_) { + Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_); + Blockly.BlockSvg.onMouseUpWrapper_ = null; + } + if (Blockly.BlockSvg.onMouseMoveWrapper_) { + Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_); + Blockly.BlockSvg.onMouseMoveWrapper_ = null; + } + var selected = Blockly.selected; + if (Blockly.dragMode_ == Blockly.DRAG_FREE) { + // Terminate a drag operation. + if (selected) { + // Update the connection locations. + var xy = selected.getRelativeToSurfaceXY(); + var dxy = goog.math.Coordinate.difference(xy, selected.dragStartXY_); + var event = new Blockly.Events.Move(selected); + event.oldCoordinate = selected.dragStartXY_; + event.recordNew(); + Blockly.Events.fire(event); + + selected.moveConnections_(dxy.x, dxy.y); + delete selected.draggedBubbles_; + selected.setDragging_(false); + selected.render(); + // Ensure that any stap and bump are part of this move's event group. + var group = Blockly.Events.getGroup(); + setTimeout(function() { + Blockly.Events.setGroup(group); + selected.snapToGrid(); + Blockly.Events.setGroup(false); + }, Blockly.BUMP_DELAY / 2); + setTimeout(function() { + Blockly.Events.setGroup(group); + selected.bumpNeighbours_(); + Blockly.Events.setGroup(false); + }, Blockly.BUMP_DELAY); + // Fire an event to allow scrollbars to resize. + selected.workspace.resizeContents(); + } + } + Blockly.dragMode_ = Blockly.DRAG_NONE; + Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); +}; + +/** + * Set parent of this block to be a new block or null. + * @param {Blockly.BlockSvg} newParent New parent block. + */ +Blockly.BlockSvg.prototype.setParent = function(newParent) { + if (newParent == this.parentBlock_) { + return; + } + var svgRoot = this.getSvgRoot(); + if (this.parentBlock_ && svgRoot) { + // Move this block up the DOM. Keep track of x/y translations. + var xy = this.getRelativeToSurfaceXY(); + this.workspace.getCanvas().appendChild(svgRoot); + svgRoot.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')'); + } + + Blockly.Field.startCache(); + Blockly.BlockSvg.superClass_.setParent.call(this, newParent); + Blockly.Field.stopCache(); + + if (newParent) { + var oldXY = this.getRelativeToSurfaceXY(); + newParent.getSvgRoot().appendChild(svgRoot); + var newXY = this.getRelativeToSurfaceXY(); + // Move the connections to match the child's new position. + this.moveConnections_(newXY.x - oldXY.x, newXY.y - oldXY.y); + } +}; + +/** + * Return the coordinates of the top-left corner of this block relative to the + * drawing surface's origin (0,0). + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() { + var x = 0; + var y = 0; + var element = this.getSvgRoot(); + if (element) { + do { + // Loop through this block and every parent. + var xy = Blockly.getRelativeXY_(element); + x += xy.x; + y += xy.y; + element = element.parentNode; + } while (element && element != this.workspace.getCanvas()); + } + return new goog.math.Coordinate(x, y); +}; + +/** + * Move a block by a relative offset. + * @param {number} dx Horizontal offset. + * @param {number} dy Vertical offset. + */ +Blockly.BlockSvg.prototype.moveBy = function(dx, dy) { + goog.asserts.assert(!this.parentBlock_, 'Block has parent.'); + var event = new Blockly.Events.Move(this); + var xy = this.getRelativeToSurfaceXY(); + this.getSvgRoot().setAttribute('transform', + 'translate(' + (xy.x + dx) + ',' + (xy.y + dy) + ')'); + this.moveConnections_(dx, dy); + event.recordNew(); + this.workspace.resizeContents(); + Blockly.Events.fire(event); +}; + +/** + * Snap this block to the nearest grid point. + */ +Blockly.BlockSvg.prototype.snapToGrid = function() { + if (!this.workspace) { + return; // Deleted block. + } + if (Blockly.dragMode_ != Blockly.DRAG_NONE) { + return; // Don't bump blocks during a drag. + } + if (this.getParent()) { + return; // Only snap top-level blocks. + } + if (this.isInFlyout) { + return; // Don't move blocks around in a flyout. + } + if (!this.workspace.options.gridOptions || + !this.workspace.options.gridOptions['snap']) { + return; // Config says no snapping. + } + var spacing = this.workspace.options.gridOptions['spacing']; + var half = spacing / 2; + var xy = this.getRelativeToSurfaceXY(); + var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x; + var dy = Math.round((xy.y - half) / spacing) * spacing + half - xy.y; + dx = Math.round(dx); + dy = Math.round(dy); + if (dx != 0 || dy != 0) { + this.moveBy(dx, dy); + } +}; + +/** + * Returns a bounding box describing the dimensions of this block + * and any blocks stacked below it. + * @return {!{height: number, width: number}} Object with height and width + * properties. + */ +Blockly.BlockSvg.prototype.getHeightWidth = function() { + var height = this.height; + var width = this.width; + // Recursively add size of subsequent blocks. + var nextBlock = this.getNextBlock(); + if (nextBlock) { + var nextHeightWidth = nextBlock.getHeightWidth(); + height += nextHeightWidth.height - 4; // Height of tab. + width = Math.max(width, nextHeightWidth.width); + } else if (!this.nextConnection && !this.outputConnection) { + // Add a bit of margin under blocks with no bottom tab. + height += 2; + } + return {height: height, width: width}; +}; + +/** + * Returns the coordinates of a bounding box describing the dimensions of this + * block and any blocks stacked below it. + * @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}} + * Object with top left and bottom right coordinates of the bounding box. + */ +Blockly.BlockSvg.prototype.getBoundingRectangle = function() { + var blockXY = this.getRelativeToSurfaceXY(this); + var tab = this.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; + var blockBounds = this.getHeightWidth(); + var topLeft; + var bottomRight; + if (this.RTL) { + // Width has the tab built into it already so subtract it here. + topLeft = new goog.math.Coordinate(blockXY.x - (blockBounds.width - tab), + blockXY.y); + // Add the width of the tab/puzzle piece knob to the x coordinate + // since X is the corner of the rectangle, not the whole puzzle piece. + bottomRight = new goog.math.Coordinate(blockXY.x + tab, + blockXY.y + blockBounds.height); + } else { + // Subtract the width of the tab/puzzle piece knob to the x coordinate + // since X is the corner of the rectangle, not the whole puzzle piece. + topLeft = new goog.math.Coordinate(blockXY.x - tab, blockXY.y); + // Width has the tab built into it already so subtract it here. + bottomRight = new goog.math.Coordinate(blockXY.x + blockBounds.width - tab, + blockXY.y + blockBounds.height); + } + return {topLeft: topLeft, bottomRight: bottomRight}; +}; + +/** + * Set whether the block is collapsed or not. + * @param {boolean} collapsed True if collapsed. + */ +Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) { + if (this.collapsed_ == collapsed) { + return; + } + var renderList = []; + // Show/hide the inputs. + for (var i = 0, input; input = this.inputList[i]; i++) { + renderList.push.apply(renderList, input.setVisible(!collapsed)); + } + + var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT'; + if (collapsed) { + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].setVisible(false); + } + var text = this.toString(Blockly.COLLAPSE_CHARS); + this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init(); + } else { + this.removeInput(COLLAPSED_INPUT_NAME); + // Clear any warnings inherited from enclosed blocks. + this.setWarningText(null); + } + Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed); + + if (!renderList.length) { + // No child blocks, just render this block. + renderList[0] = this; + } + if (this.rendered) { + for (var i = 0, block; block = renderList[i]; i++) { + block.render(); + } + // Don't bump neighbours. + // Although bumping neighbours would make sense, users often collapse + // all their functions and store them next to each other. Expanding and + // bumping causes all their definitions to go out of alignment. + } +}; + +/** + * Open the next (or previous) FieldTextInput. + * @param {Blockly.Field|Blockly.Block} start Current location. + * @param {boolean} forward If true go forward, otherwise backward. + */ +Blockly.BlockSvg.prototype.tab = function(start, forward) { + // This function need not be efficient since it runs once on a keypress. + // Create an ordered list of all text fields and connected inputs. + var list = []; + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldTextInput) { + // TODO: Also support dropdown fields. + list.push(field); + } + } + if (input.connection) { + var block = input.connection.targetBlock(); + if (block) { + list.push(block); + } + } + } + var i = list.indexOf(start); + if (i == -1) { + // No start location, start at the beginning or end. + i = forward ? -1 : list.length; + } + var target = list[forward ? i + 1 : i - 1]; + if (!target) { + // Ran off of list. + var parent = this.getParent(); + if (parent) { + parent.tab(this, forward); + } + } else if (target instanceof Blockly.Field) { + target.showEditor_(); + } else { + target.tab(null, forward); + } +}; + +/** + * Handle a mouse-down on an SVG block. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.BlockSvg.prototype.onMouseDown_ = function(e) { + if (this.workspace.options.readOnly) { + return; + } + if (this.isInFlyout) { + return; + } + if (this.isInMutator) { + // Mutator's coordinate system could be out of date because the bubble was + // dragged, the block was moved, the parent workspace zoomed, etc. + this.workspace.resize(); + } + + this.workspace.updateScreenCalculationsIfScrolled(); + this.workspace.markFocused(); + Blockly.terminateDrag_(); + this.select(); + Blockly.hideChaff(); + if (Blockly.isRightButton(e)) { + // Right-click. + this.showContextMenu_(e); + } else if (!this.isMovable()) { + // Allow immovable blocks to be selected and context menued, but not + // dragged. Let this event bubble up to document, so the workspace may be + // dragged instead. + return; + } else { + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + // Left-click (or middle click) + Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); + + this.dragStartXY_ = this.getRelativeToSurfaceXY(); + this.workspace.startDrag(e, this.dragStartXY_); + + Blockly.dragMode_ = Blockly.DRAG_STICKY; + Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEvent_(document, + 'mouseup', this, this.onMouseUp_); + Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEvent_(document, + 'mousemove', this, this.onMouseMove_); + // Build a list of bubbles that need to be moved and where they started. + this.draggedBubbles_ = []; + var descendants = this.getDescendants(); + for (var i = 0, descendant; descendant = descendants[i]; i++) { + var icons = descendant.getIcons(); + for (var j = 0; j < icons.length; j++) { + var data = icons[j].getIconLocation(); + data.bubble = icons[j]; + this.draggedBubbles_.push(data); + } + } + } + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); + e.preventDefault(); +}; + +/** + * Handle a mouse-up anywhere in the SVG pane. Is only registered when a + * block is clicked. We can't use mouseUp on the block since a fast-moving + * cursor can briefly escape the block before it catches up. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.BlockSvg.prototype.onMouseUp_ = function(e) { + if (Blockly.dragMode_ != Blockly.DRAG_FREE && + !Blockly.WidgetDiv.isVisible()) { + Blockly.Events.fire( + new Blockly.Events.Ui(this, 'click', undefined, undefined)); + } + Blockly.terminateDrag_(); + if (Blockly.selected && Blockly.highlightedConnection_) { + // Connect two blocks together. + Blockly.localConnection_.connect(Blockly.highlightedConnection_); + if (this.rendered) { + // Trigger a connection animation. + // Determine which connection is inferior (lower in the source stack). + var inferiorConnection = Blockly.localConnection_.isSuperior() ? + Blockly.highlightedConnection_ : Blockly.localConnection_; + inferiorConnection.getSourceBlock().connectionUiEffect(); + } + if (this.workspace.trashcan) { + // Don't throw an object in the trash can if it just got connected. + this.workspace.trashcan.close(); + } + } else if (!this.getParent() && Blockly.selected.isDeletable() && + this.workspace.isDeleteArea(e)) { + var trashcan = this.workspace.trashcan; + if (trashcan) { + goog.Timer.callOnce(trashcan.close, 100, trashcan); + } + Blockly.selected.dispose(false, true); + } + if (Blockly.highlightedConnection_) { + Blockly.highlightedConnection_.unhighlight(); + Blockly.highlightedConnection_ = null; + } + Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); + if (!Blockly.WidgetDiv.isVisible()) { + Blockly.Events.setGroup(false); + } +}; + +/** + * Load the block's help page in a new window. + * @private + */ +Blockly.BlockSvg.prototype.showHelp_ = function() { + var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl; + if (url) { + window.open(url); + } +}; + +/** + * Show the context menu for this block. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { + if (this.workspace.options.readOnly || !this.contextMenu) { + return; + } + // Save the current block in a variable for use in closures. + var block = this; + var menuOptions = []; + + if (this.isDeletable() && this.isMovable() && !block.isInFlyout) { + // Option to duplicate this block. + var duplicateOption = { + text: Blockly.Msg.DUPLICATE_BLOCK, + enabled: true, + callback: function() { + Blockly.duplicate_(block); + } + }; + if (this.getDescendants().length > this.workspace.remainingCapacity()) { + duplicateOption.enabled = false; + } + menuOptions.push(duplicateOption); + + if (this.isEditable() && !this.collapsed_ && + this.workspace.options.comments) { + // Option to add/remove a comment. + var commentOption = {enabled: !goog.userAgent.IE}; + if (this.comment) { + commentOption.text = Blockly.Msg.REMOVE_COMMENT; + commentOption.callback = function() { + block.setCommentText(null); + }; + } else { + commentOption.text = Blockly.Msg.ADD_COMMENT; + commentOption.callback = function() { + block.setCommentText(''); + }; + } + menuOptions.push(commentOption); + } + + // Option to make block inline. + if (!this.collapsed_) { + for (var i = 1; i < this.inputList.length; i++) { + if (this.inputList[i - 1].type != Blockly.NEXT_STATEMENT && + this.inputList[i].type != Blockly.NEXT_STATEMENT) { + // Only display this option if there are two value or dummy inputs + // next to each other. + var inlineOption = {enabled: true}; + var isInline = this.getInputsInline(); + inlineOption.text = isInline ? + Blockly.Msg.EXTERNAL_INPUTS : Blockly.Msg.INLINE_INPUTS; + inlineOption.callback = function() { + block.setInputsInline(!isInline); + }; + menuOptions.push(inlineOption); + break; + } + } + } + + if (this.workspace.options.collapse) { + // Option to collapse/expand block. + if (this.collapsed_) { + var expandOption = {enabled: true}; + expandOption.text = Blockly.Msg.EXPAND_BLOCK; + expandOption.callback = function() { + block.setCollapsed(false); + }; + menuOptions.push(expandOption); + } else { + var collapseOption = {enabled: true}; + collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK; + collapseOption.callback = function() { + block.setCollapsed(true); + }; + menuOptions.push(collapseOption); + } + } + + if (this.workspace.options.disable) { + // Option to disable/enable block. + var disableOption = { + text: this.disabled ? + Blockly.Msg.ENABLE_BLOCK : Blockly.Msg.DISABLE_BLOCK, + enabled: !this.getInheritedDisabled(), + callback: function() { + block.setDisabled(!block.disabled); + } + }; + menuOptions.push(disableOption); + } + + // Option to delete this block. + // Count the number of blocks that are nested in this block. + var descendantCount = this.getDescendants().length; + var nextBlock = this.getNextBlock(); + if (nextBlock) { + // Blocks in the current stack would survive this block's deletion. + descendantCount -= nextBlock.getDescendants().length; + } + var deleteOption = { + text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK : + Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)), + enabled: true, + callback: function() { + Blockly.Events.setGroup(true); + block.dispose(true, true); + Blockly.Events.setGroup(false); + } + }; + menuOptions.push(deleteOption); + } + + // Option to get help. + var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl; + var helpOption = {enabled: !!url}; + helpOption.text = Blockly.Msg.HELP; + helpOption.callback = function() { + block.showHelp_(); + }; + menuOptions.push(helpOption); + + // Allow the block to add or modify menuOptions. + if (this.customContextMenu && !block.isInFlyout) { + this.customContextMenu(menuOptions); + } + + Blockly.ContextMenu.show(e, menuOptions, this.RTL); + Blockly.ContextMenu.currentBlock = this; +}; + +/** + * Move the connections for this block and all blocks attached under it. + * Also update any attached bubbles. + * @param {number} dx Horizontal offset from current location. + * @param {number} dy Vertical offset from current location. + * @private + */ +Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) { + if (!this.rendered) { + // Rendering is required to lay out the blocks. + // This is probably an invisible block attached to a collapsed block. + return; + } + var myConnections = this.getConnections_(false); + for (var i = 0; i < myConnections.length; i++) { + myConnections[i].moveBy(dx, dy); + } + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].computeIconLocation(); + } + + // Recurse through all blocks attached under this one. + for (var i = 0; i < this.childBlocks_.length; i++) { + this.childBlocks_[i].moveConnections_(dx, dy); + } +}; + +/** + * Recursively adds or removes the dragging class to this node and its children. + * @param {boolean} adding True if adding, false if removing. + * @private + */ +Blockly.BlockSvg.prototype.setDragging_ = function(adding) { + if (adding) { + var group = this.getSvgRoot(); + group.translate_ = ''; + group.skew_ = ''; + this.addDragging(); + Blockly.draggingConnections_ = + Blockly.draggingConnections_.concat(this.getConnections_(true)); + } else { + this.removeDragging(); + Blockly.draggingConnections_ = []; + } + // Recurse through all blocks attached under this one. + for (var i = 0; i < this.childBlocks_.length; i++) { + this.childBlocks_[i].setDragging_(adding); + } +}; + +/** + * Drag this block to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ +Blockly.BlockSvg.prototype.onMouseMove_ = function(e) { + if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 && + e.button == 0) { + /* HACK: + Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove + events on certain touch actions. Ignore events with these signatures. + This may result in a one-pixel blind spot in other browsers, + but this shouldn't be noticeable. */ + e.stopPropagation(); + return; + } + + var oldXY = this.getRelativeToSurfaceXY(); + var newXY = this.workspace.moveDrag(e); + + if (Blockly.dragMode_ == Blockly.DRAG_STICKY) { + // Still dragging within the sticky DRAG_RADIUS. + var dr = goog.math.Coordinate.distance(oldXY, newXY) * this.workspace.scale; + if (dr > Blockly.DRAG_RADIUS) { + // Switch to unrestricted dragging. + Blockly.dragMode_ = Blockly.DRAG_FREE; + Blockly.longStop_(); + if (this.parentBlock_) { + // Push this block to the very top of the stack. + this.unplug(); + var group = this.getSvgRoot(); + group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')'; + this.disconnectUiEffect(); + } + this.setDragging_(true); + } + } + if (Blockly.dragMode_ == Blockly.DRAG_FREE) { + // Unrestricted dragging. + var dxy = goog.math.Coordinate.difference(oldXY, this.dragStartXY_); + var group = this.getSvgRoot(); + group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')'; + group.setAttribute('transform', group.translate_ + group.skew_); + // Drag all the nested bubbles. + for (var i = 0; i < this.draggedBubbles_.length; i++) { + var commentData = this.draggedBubbles_[i]; + commentData.bubble.setIconLocation( + goog.math.Coordinate.sum(commentData, dxy)); + } + + // Check to see if any of this block's connections are within range of + // another block's connection. + var myConnections = this.getConnections_(false); + // Also check the last connection on this stack + var lastOnStack = this.lastConnectionInStack_(); + if (lastOnStack && lastOnStack != this.nextConnection) { + myConnections.push(lastOnStack); + } + var closestConnection = null; + var localConnection = null; + var radiusConnection = Blockly.SNAP_RADIUS; + for (var i = 0; i < myConnections.length; i++) { + var myConnection = myConnections[i]; + var neighbour = myConnection.closest(radiusConnection, dxy); + if (neighbour.connection) { + closestConnection = neighbour.connection; + localConnection = myConnection; + radiusConnection = neighbour.radius; + } + } + + // Remove connection highlighting if needed. + if (Blockly.highlightedConnection_ && + Blockly.highlightedConnection_ != closestConnection) { + Blockly.highlightedConnection_.unhighlight(); + Blockly.highlightedConnection_ = null; + Blockly.localConnection_ = null; + } + // Add connection highlighting if needed. + if (closestConnection && + closestConnection != Blockly.highlightedConnection_) { + closestConnection.highlight(); + Blockly.highlightedConnection_ = closestConnection; + Blockly.localConnection_ = localConnection; + } + // Provide visual indication of whether the block will be deleted if + // dropped here. + if (this.isDeletable()) { + this.workspace.isDeleteArea(e); + } + } + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); + e.preventDefault(); +}; + +/** + * Add or remove the UI indicating if this block is movable or not. + */ +Blockly.BlockSvg.prototype.updateMovable = function() { + if (this.isMovable()) { + Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDraggable'); + } else { + Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDraggable'); + } +}; + +/** + * Set whether this block is movable or not. + * @param {boolean} movable True if movable. + */ +Blockly.BlockSvg.prototype.setMovable = function(movable) { + Blockly.BlockSvg.superClass_.setMovable.call(this, movable); + this.updateMovable(); +}; + +/** + * Set whether this block is editable or not. + * @param {boolean} editable True if editable. + */ +Blockly.BlockSvg.prototype.setEditable = function(editable) { + Blockly.BlockSvg.superClass_.setEditable.call(this, editable); + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].updateEditable(); + } +}; + +/** + * Set whether this block is a shadow block or not. + * @param {boolean} shadow True if a shadow. + */ +Blockly.BlockSvg.prototype.setShadow = function(shadow) { + Blockly.BlockSvg.superClass_.setShadow.call(this, shadow); + this.updateColour(); +}; + +/** + * Return the root node of the SVG or null if none exists. + * @return {Element} The root SVG node (probably a group). + */ +Blockly.BlockSvg.prototype.getSvgRoot = function() { + return this.svgGroup_; +}; + +/** + * Dispose of this block. + * @param {boolean} healStack If true, then try to heal any gap by connecting + * the next statement with the previous statement. Otherwise, dispose of + * all children of this block. + * @param {boolean} animate If true, show a disposal animation and sound. + */ +Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { + if (!this.workspace) { + // The block has already been deleted. + return; + } + Blockly.Tooltip.hide(); + Blockly.Field.startCache(); + // Save the block's workspace temporarily so we can resize the + // contents once the block is disposed. + var blockWorkspace = this.workspace; + // If this block is being dragged, unlink the mouse events. + if (Blockly.selected == this) { + this.unselect(); + Blockly.terminateDrag_(); + } + // If this block has a context menu open, close it. + if (Blockly.ContextMenu.currentBlock == this) { + Blockly.ContextMenu.hide(); + } + + if (animate && this.rendered) { + this.unplug(healStack); + this.disposeUiEffect(); + } + // Stop rerendering. + this.rendered = false; + + Blockly.Events.disable(); + try { + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].dispose(); + } + } finally { + Blockly.Events.enable(); + } + Blockly.BlockSvg.superClass_.dispose.call(this, healStack); + + goog.dom.removeNode(this.svgGroup_); + blockWorkspace.resizeContents(); + // Sever JavaScript to DOM connections. + this.svgGroup_ = null; + this.svgPath_ = null; + this.svgPathLight_ = null; + this.svgPathDark_ = null; + Blockly.Field.stopCache(); +}; + +/** + * Play some UI effects (sound, animation) when disposing of a block. + */ +Blockly.BlockSvg.prototype.disposeUiEffect = function() { + this.workspace.playAudio('delete'); + + var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_), + this.workspace); + // Deeply clone the current block. + var clone = this.svgGroup_.cloneNode(true); + clone.translateX_ = xy.x; + clone.translateY_ = xy.y; + clone.setAttribute('transform', + 'translate(' + clone.translateX_ + ',' + clone.translateY_ + ')'); + this.workspace.getParentSvg().appendChild(clone); + clone.bBox_ = clone.getBBox(); + // Start the animation. + Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date, + this.workspace.scale); +}; + +/** + * Animate a cloned block and eventually dispose of it. + * This is a class method, not an instace method since the original block has + * been destroyed and is no longer accessible. + * @param {!Element} clone SVG element to animate and dispose of. + * @param {boolean} rtl True if RTL, false if LTR. + * @param {!Date} start Date of animation's start. + * @param {number} workspaceScale Scale of workspace. + * @private + */ +Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) { + var ms = new Date - start; + var percent = ms / 150; + if (percent > 1) { + goog.dom.removeNode(clone); + } else { + var x = clone.translateX_ + + (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent; + var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent; + var scale = (1 - percent) * workspaceScale; + clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' + + ' scale(' + scale + ')'); + var closure = function() { + Blockly.BlockSvg.disposeUiStep_(clone, rtl, start, workspaceScale); + }; + setTimeout(closure, 10); + } +}; + +/** + * Play some UI effects (sound, ripple) after a connection has been established. + */ +Blockly.BlockSvg.prototype.connectionUiEffect = function() { + this.workspace.playAudio('click'); + if (this.workspace.scale < 1) { + return; // Too small to care about visual effects. + } + // Determine the absolute coordinates of the inferior block. + var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_), + this.workspace); + // Offset the coordinates based on the two connection types, fix scale. + if (this.outputConnection) { + xy.x += (this.RTL ? 3 : -3) * this.workspace.scale; + xy.y += 13 * this.workspace.scale; + } else if (this.previousConnection) { + xy.x += (this.RTL ? -23 : 23) * this.workspace.scale; + xy.y += 3 * this.workspace.scale; + } + var ripple = Blockly.createSvgElement('circle', + {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none', + 'stroke': '#888', 'stroke-width': 10}, + this.workspace.getParentSvg()); + // Start the animation. + Blockly.BlockSvg.connectionUiStep_(ripple, new Date, this.workspace.scale); +}; + +/** + * Expand a ripple around a connection. + * @param {!Element} ripple Element to animate. + * @param {!Date} start Date of animation's start. + * @param {number} workspaceScale Scale of workspace. + * @private + */ +Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) { + var ms = new Date - start; + var percent = ms / 150; + if (percent > 1) { + goog.dom.removeNode(ripple); + } else { + ripple.setAttribute('r', percent * 25 * workspaceScale); + ripple.style.opacity = 1 - percent; + var closure = function() { + Blockly.BlockSvg.connectionUiStep_(ripple, start, workspaceScale); + }; + Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(closure, 10); + } +}; + +/** + * Play some UI effects (sound, animation) when disconnecting a block. + */ +Blockly.BlockSvg.prototype.disconnectUiEffect = function() { + this.workspace.playAudio('disconnect'); + if (this.workspace.scale < 1) { + return; // Too small to care about visual effects. + } + // Horizontal distance for bottom of block to wiggle. + var DISPLACEMENT = 10; + // Scale magnitude of skew to height of block. + var height = this.getHeightWidth().height; + var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180; + if (!this.RTL) { + magnitude *= -1; + } + // Start the animation. + Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date); +}; + +/** + * Animate a brief wiggle of a disconnected block. + * @param {!Element} group SVG element to animate. + * @param {number} magnitude Maximum degrees skew (reversed for RTL). + * @param {!Date} start Date of animation's start. + * @private + */ +Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) { + var DURATION = 200; // Milliseconds. + var WIGGLES = 3; // Half oscillations. + + var ms = new Date - start; + var percent = ms / DURATION; + + if (percent > 1) { + group.skew_ = ''; + } else { + var skew = Math.round(Math.sin(percent * Math.PI * WIGGLES) * + (1 - percent) * magnitude); + group.skew_ = 'skewX(' + skew + ')'; + var closure = function() { + Blockly.BlockSvg.disconnectUiStep_(group, magnitude, start); + }; + Blockly.BlockSvg.disconnectUiStop_.group = group; + Blockly.BlockSvg.disconnectUiStop_.pid = setTimeout(closure, 10); + } + group.setAttribute('transform', group.translate_ + group.skew_); +}; + +/** + * Stop the disconnect UI animation immediately. + * @private + */ +Blockly.BlockSvg.disconnectUiStop_ = function() { + if (Blockly.BlockSvg.disconnectUiStop_.group) { + clearTimeout(Blockly.BlockSvg.disconnectUiStop_.pid); + var group = Blockly.BlockSvg.disconnectUiStop_.group; + group.skew_ = ''; + group.setAttribute('transform', group.translate_); + Blockly.BlockSvg.disconnectUiStop_.group = null; + } +}; + +/** + * PID of disconnect UI animation. There can only be one at a time. + * @type {number} + */ +Blockly.BlockSvg.disconnectUiStop_.pid = 0; + +/** + * SVG group of wobbling block. There can only be one at a time. + * @type {Element} + */ +Blockly.BlockSvg.disconnectUiStop_.group = null; + +/** + * Change the colour of a block. + */ +Blockly.BlockSvg.prototype.updateColour = function() { + if (this.disabled) { + // Disabled blocks don't have colour. + return; + } + var hexColour = this.getColour(); + var rgb = goog.color.hexToRgb(hexColour); + if (this.isShadow()) { + rgb = goog.color.lighten(rgb, 0.6); + hexColour = goog.color.rgbArrayToHex(rgb); + this.svgPathLight_.style.display = 'none'; + this.svgPathDark_.setAttribute('fill', hexColour); + } else { + this.svgPathLight_.style.display = ''; + var hexLight = goog.color.rgbArrayToHex(goog.color.lighten(rgb, 0.3)); + var hexDark = goog.color.rgbArrayToHex(goog.color.darken(rgb, 0.2)); + this.svgPathLight_.setAttribute('stroke', hexLight); + this.svgPathDark_.setAttribute('fill', hexDark); + } + this.svgPath_.setAttribute('fill', hexColour); + + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].updateColour(); + } + + // Bump every dropdown to change its colour. + for (var x = 0, input; input = this.inputList[x]; x++) { + for (var y = 0, field; field = input.fieldRow[y]; y++) { + field.setText(null); + } + } +}; + +/** + * Enable or disable a block. + */ +Blockly.BlockSvg.prototype.updateDisabled = function() { + var hasClass = Blockly.hasClass_(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDisabled'); + if (this.disabled || this.getInheritedDisabled()) { + if (!hasClass) { + Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDisabled'); + this.svgPath_.setAttribute('fill', + 'url(#' + this.workspace.options.disabledPatternId + ')'); + } + } else { + if (hasClass) { + Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDisabled'); + this.updateColour(); + } + } + var children = this.getChildren(); + for (var i = 0, child; child = children[i]; i++) { + child.updateDisabled(); + } +}; + +/** + * Returns the comment on this block (or '' if none). + * @return {string} Block's comment. + */ +Blockly.BlockSvg.prototype.getCommentText = function() { + if (this.comment) { + var comment = this.comment.getText(); + // Trim off trailing whitespace. + return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n'); + } + return ''; +}; + +/** + * Set this block's comment text. + * @param {?string} text The text, or null to delete. + */ +Blockly.BlockSvg.prototype.setCommentText = function(text) { + var changedState = false; + if (goog.isString(text)) { + if (!this.comment) { + this.comment = new Blockly.Comment(this); + changedState = true; + } + this.comment.setText(/** @type {string} */ (text)); + } else { + if (this.comment) { + this.comment.dispose(); + changedState = true; + } + } + if (changedState && this.rendered) { + this.render(); + // Adding or removing a comment icon will cause the block to change shape. + this.bumpNeighbours_(); + } +}; + +/** + * Set this block's warning text. + * @param {?string} text The text, or null to delete. + * @param {string=} opt_id An optional ID for the warning text to be able to + * maintain multiple warnings. + */ +Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) { + if (!this.setWarningText.pid_) { + // Create a database of warning PIDs. + // Only runs once per block (and only those with warnings). + this.setWarningText.pid_ = Object.create(null); + } + var id = opt_id || ''; + if (!id) { + // Kill all previous pending processes, this edit supercedes them all. + for (var n in this.setWarningText.pid_) { + clearTimeout(this.setWarningText.pid_[n]); + delete this.setWarningText.pid_[n]; + } + } else if (this.setWarningText.pid_[id]) { + // Only queue up the latest change. Kill any earlier pending process. + clearTimeout(this.setWarningText.pid_[id]); + delete this.setWarningText.pid_[id]; + } + if (Blockly.dragMode_ == Blockly.DRAG_FREE) { + // Don't change the warning text during a drag. + // Wait until the drag finishes. + var thisBlock = this; + this.setWarningText.pid_[id] = setTimeout(function() { + if (thisBlock.workspace) { // Check block wasn't deleted. + delete thisBlock.setWarningText.pid_[id]; + thisBlock.setWarningText(text, id); + } + }, 100); + return; + } + if (this.isInFlyout) { + text = null; + } + + // Bubble up to add a warning on top-most collapsed block. + var parent = this.getSurroundParent(); + var collapsedParent = null; + while (parent) { + if (parent.isCollapsed()) { + collapsedParent = parent; + } + parent = parent.getSurroundParent(); + } + if (collapsedParent) { + collapsedParent.setWarningText(text, 'collapsed ' + this.id + ' ' + id); + } + + var changedState = false; + if (goog.isString(text)) { + if (!this.warning) { + this.warning = new Blockly.Warning(this); + changedState = true; + } + this.warning.setText(/** @type {string} */ (text), id); + } else { + // Dispose all warnings if no id is given. + if (this.warning && !id) { + this.warning.dispose(); + changedState = true; + } else if (this.warning) { + var oldText = this.warning.getText(); + this.warning.setText('', id); + var newText = this.warning.getText(); + if (!newText) { + this.warning.dispose(); + } + changedState = oldText == newText; + } + } + if (changedState && this.rendered) { + this.render(); + // Adding or removing a warning icon will cause the block to change shape. + this.bumpNeighbours_(); + } +}; + +/** + * Give this block a mutator dialog. + * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove. + */ +Blockly.BlockSvg.prototype.setMutator = function(mutator) { + if (this.mutator && this.mutator !== mutator) { + this.mutator.dispose(); + } + if (mutator) { + mutator.block_ = this; + this.mutator = mutator; + mutator.createIcon(); + } +}; + +/** + * Set whether the block is disabled or not. + * @param {boolean} disabled True if disabled. + */ +Blockly.BlockSvg.prototype.setDisabled = function(disabled) { + if (this.disabled != disabled) { + Blockly.BlockSvg.superClass_.setDisabled.call(this, disabled); + if (this.rendered) { + this.updateDisabled(); + } + } +}; + +/** + * Select this block. Highlight it visually. + */ +Blockly.BlockSvg.prototype.addSelect = function() { + Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), + 'blocklySelected'); + // Move the selected block to the top of the stack. + var block = this; + do { + var root = block.getSvgRoot(); + root.parentNode.appendChild(root); + block = block.getParent(); + } while (block); +}; + +/** + * Unselect this block. Remove its highlighting. + */ +Blockly.BlockSvg.prototype.removeSelect = function() { + Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), + 'blocklySelected'); +}; + +/** + * Adds the dragging class to this block. + * Also disables the highlights/shadows to improve performance. + */ +Blockly.BlockSvg.prototype.addDragging = function() { + Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDragging'); +}; + +/** + * Removes the dragging class from this block. + */ +Blockly.BlockSvg.prototype.removeDragging = function() { + Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDragging'); +}; + +// Overrides of functions on Blockly.Block that take into account whether the +// block has been rendered. + +/** + * Change the colour of a block. + * @param {number|string} colour HSV hue value, or #RRGGBB string. + */ +Blockly.BlockSvg.prototype.setColour = function(colour) { + Blockly.BlockSvg.superClass_.setColour.call(this, colour); + + if (this.rendered) { + this.updateColour(); + } +}; + +/** + * Set whether this block can chain onto the bottom of another block. + * @param {boolean} newBoolean True if there can be a previous statement. + * @param {string|Array.|null|undefined} opt_check Statement type or + * list of statement types. Null/undefined if any type could be connected. + */ +Blockly.BlockSvg.prototype.setPreviousStatement = + function(newBoolean, opt_check) { + /* eslint-disable indent */ + Blockly.BlockSvg.superClass_.setPreviousStatement.call(this, newBoolean, + opt_check); + + if (this.rendered) { + this.render(); + this.bumpNeighbours_(); + } +}; /* eslint-enable indent */ + +/** + * Set whether another block can chain onto the bottom of this block. + * @param {boolean} newBoolean True if there can be a next statement. + * @param {string|Array.|null|undefined} opt_check Statement type or + * list of statement types. Null/undefined if any type could be connected. + */ +Blockly.BlockSvg.prototype.setNextStatement = function(newBoolean, opt_check) { + Blockly.BlockSvg.superClass_.setNextStatement.call(this, newBoolean, + opt_check); + + if (this.rendered) { + this.render(); + this.bumpNeighbours_(); + } +}; + +/** + * Set whether this block returns a value. + * @param {boolean} newBoolean True if there is an output. + * @param {string|Array.|null|undefined} opt_check Returned type or list + * of returned types. Null or undefined if any type could be returned + * (e.g. variable get). + */ +Blockly.BlockSvg.prototype.setOutput = function(newBoolean, opt_check) { + Blockly.BlockSvg.superClass_.setOutput.call(this, newBoolean, opt_check); + + if (this.rendered) { + this.render(); + this.bumpNeighbours_(); + } +}; + +/** + * Set whether value inputs are arranged horizontally or vertically. + * @param {boolean} newBoolean True if inputs are horizontal. + */ +Blockly.BlockSvg.prototype.setInputsInline = function(newBoolean) { + Blockly.BlockSvg.superClass_.setInputsInline.call(this, newBoolean); + + if (this.rendered) { + this.render(); + this.bumpNeighbours_(); + } +}; + +/** + * Remove an input from this block. + * @param {string} name The name of the input. + * @param {boolean=} opt_quiet True to prevent error if input is not present. + * @throws {goog.asserts.AssertionError} if the input is not present and + * opt_quiet is not true. + */ +Blockly.BlockSvg.prototype.removeInput = function(name, opt_quiet) { + Blockly.BlockSvg.superClass_.removeInput.call(this, name, opt_quiet); + + if (this.rendered) { + this.render(); + // Removing an input will cause the block to change shape. + this.bumpNeighbours_(); + } +}; + +/** + * Move a numbered input to a different location on this block. + * @param {number} inputIndex Index of the input to move. + * @param {number} refIndex Index of input that should be after the moved input. + */ +Blockly.BlockSvg.prototype.moveNumberedInputBefore = function( + inputIndex, refIndex) { + Blockly.BlockSvg.superClass_.moveNumberedInputBefore.call(this, inputIndex, + refIndex); + + if (this.rendered) { + this.render(); + // Moving an input will cause the block to change shape. + this.bumpNeighbours_(); + } +}; + +/** + * Add a value input, statement input or local variable to this block. + * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or + * Blockly.DUMMY_INPUT. + * @param {string} name Language-neutral identifier which may used to find this + * input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + * @private + */ +Blockly.BlockSvg.prototype.appendInput_ = function(type, name) { + var input = Blockly.BlockSvg.superClass_.appendInput_.call(this, type, name); + + if (this.rendered) { + this.render(); + // Adding an input will cause the block to change shape. + this.bumpNeighbours_(); + } + return input; +}; + +/** + * Returns connections originating from this block. + * @param {boolean} all If true, return all connections even hidden ones. + * Otherwise, for a non-rendered block return an empty list, and for a + * collapsed block don't return inputs connections. + * @return {!Array.} Array of connections. + * @private + */ +Blockly.BlockSvg.prototype.getConnections_ = function(all) { + var myConnections = []; + if (all || this.rendered) { + if (this.outputConnection) { + myConnections.push(this.outputConnection); + } + if (this.previousConnection) { + myConnections.push(this.previousConnection); + } + if (this.nextConnection) { + myConnections.push(this.nextConnection); + } + if (all || !this.collapsed_) { + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.connection) { + myConnections.push(input.connection); + } + } + } + } + return myConnections; +}; + +/** + * Create a connection of the specified type. + * @param {number} type The type of the connection to create. + * @return {!Blockly.RenderedConnection} A new connection of the specified type. + * @private + */ +Blockly.BlockSvg.prototype.makeConnection_ = function(type) { + return new Blockly.RenderedConnection(this, type); +}; diff --git a/src/blockly/core/blockly.js b/src/blockly/core/blockly.js new file mode 100644 index 0000000..fb6562e --- /dev/null +++ b/src/blockly/core/blockly.js @@ -0,0 +1,453 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Core JavaScript library for Blockly. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +// Top level object for Blockly. +goog.provide('Blockly'); + +goog.require('Blockly.BlockSvg.render'); +goog.require('Blockly.Events'); +goog.require('Blockly.FieldAngle'); +goog.require('Blockly.FieldCheckbox'); +goog.require('Blockly.FieldColour'); +// Date picker commented out since it increases footprint by 60%. +// Add it only if you need it. +//goog.require('Blockly.FieldDate'); +goog.require('Blockly.FieldDropdown'); +goog.require('Blockly.FieldImage'); +goog.require('Blockly.FieldTextInput'); +goog.require('Blockly.FieldNumber'); +goog.require('Blockly.FieldVariable'); +goog.require('Blockly.Generator'); +goog.require('Blockly.Msg'); +goog.require('Blockly.Procedures'); +goog.require('Blockly.Toolbox'); +goog.require('Blockly.WidgetDiv'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('Blockly.constants'); +goog.require('Blockly.inject'); +goog.require('Blockly.utils'); +goog.require('goog.color'); +goog.require('goog.userAgent'); + + +// Turn off debugging when compiled. +var CLOSURE_DEFINES = {'goog.DEBUG': false}; + +/** + * The main workspace most recently used. + * Set by Blockly.WorkspaceSvg.prototype.markFocused + * @type {Blockly.Workspace} + */ +Blockly.mainWorkspace = null; + +/** + * Currently selected block. + * @type {Blockly.Block} + */ +Blockly.selected = null; + +/** + * Currently highlighted connection (during a drag). + * @type {Blockly.Connection} + * @private + */ +Blockly.highlightedConnection_ = null; + +/** + * Connection on dragged block that matches the highlighted connection. + * @type {Blockly.Connection} + * @private + */ +Blockly.localConnection_ = null; + +/** + * All of the connections on blocks that are currently being dragged. + * @type {!Array.} + * @private + */ +Blockly.draggingConnections_ = []; + +/** + * Contents of the local clipboard. + * @type {Element} + * @private + */ +Blockly.clipboardXml_ = null; + +/** + * Source of the local clipboard. + * @type {Blockly.WorkspaceSvg} + * @private + */ +Blockly.clipboardSource_ = null; + +/** + * Is the mouse dragging a block? + * DRAG_NONE - No drag operation. + * DRAG_STICKY - Still inside the sticky DRAG_RADIUS. + * DRAG_FREE - Freely draggable. + * @private + */ +Blockly.dragMode_ = Blockly.DRAG_NONE; + +/** + * Wrapper function called when a touch mouseUp occurs during a drag operation. + * @type {Array.} + * @private + */ +Blockly.onTouchUpWrapper_ = null; + +/** + * Convert a hue (HSV model) into an RGB hex triplet. + * @param {number} hue Hue on a colour wheel (0-360). + * @return {string} RGB code, e.g. '#5ba65b'. + */ +Blockly.hueToRgb = function(hue) { + return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION, + Blockly.HSV_VALUE * 255); +}; + +/** + * Returns the dimensions of the specified SVG image. + * @param {!Element} svg SVG image. + * @return {!Object} Contains width and height properties. + */ +Blockly.svgSize = function(svg) { + return {width: svg.cachedWidth_, + height: svg.cachedHeight_}; +}; + +/** + * Size the workspace when the contents change. This also updates + * scrollbars accordingly. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize. + */ +Blockly.resizeSvgContents = function(workspace) { + workspace.resizeContents(); +}; + +/** + * Size the SVG image to completely fill its container. Call this when the view + * actually changes sizes (e.g. on a window resize/device orientation change). + * See Blockly.resizeSvgContents to resize the workspace when the contents + * change (e.g. when a block is added or removed). + * Record the height/width of the SVG image. + * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG. + */ +Blockly.svgResize = function(workspace) { + var mainWorkspace = workspace; + while (mainWorkspace.options.parentWorkspace) { + mainWorkspace = mainWorkspace.options.parentWorkspace; + } + var svg = mainWorkspace.getParentSvg(); + var div = svg.parentNode; + if (!div) { + // Workspace deleted, or something. + return; + } + var width = div.offsetWidth; + var height = div.offsetHeight; + if (svg.cachedWidth_ != width) { + svg.setAttribute('width', width + 'px'); + svg.cachedWidth_ = width; + } + if (svg.cachedHeight_ != height) { + svg.setAttribute('height', height + 'px'); + svg.cachedHeight_ = height; + } + mainWorkspace.resize(); +}; + +/** + * Handle a mouse-up anywhere on the page. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.onMouseUp_ = function(e) { + var workspace = Blockly.getMainWorkspace(); + Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); + workspace.dragMode_ = Blockly.DRAG_NONE; + // Unbind the touch event if it exists. + if (Blockly.onTouchUpWrapper_) { + Blockly.unbindEvent_(Blockly.onTouchUpWrapper_); + Blockly.onTouchUpWrapper_ = null; + } + if (Blockly.onMouseMoveWrapper_) { + Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_); + Blockly.onMouseMoveWrapper_ = null; + } +}; + +/** + * Handle a mouse-move on SVG drawing surface. + * @param {!Event} e Mouse move event. + * @private + */ +Blockly.onMouseMove_ = function(e) { + if (e.touches && e.touches.length >= 2) { + return; // Multi-touch gestures won't have e.clientX. + } + var workspace = Blockly.getMainWorkspace(); + if (workspace.dragMode_ != Blockly.DRAG_NONE) { + var dx = e.clientX - workspace.startDragMouseX; + var dy = e.clientY - workspace.startDragMouseY; + var metrics = workspace.startDragMetrics; + var x = workspace.startScrollX + dx; + var y = workspace.startScrollY + dy; + x = Math.min(x, -metrics.contentLeft); + y = Math.min(y, -metrics.contentTop); + x = Math.max(x, metrics.viewWidth - metrics.contentLeft - + metrics.contentWidth); + y = Math.max(y, metrics.viewHeight - metrics.contentTop - + metrics.contentHeight); + + // Move the scrollbars and the page will scroll automatically. + workspace.scrollbar.set(-x - metrics.contentLeft, + -y - metrics.contentTop); + // Cancel the long-press if the drag has moved too far. + if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) { + Blockly.longStop_(); + workspace.dragMode_ = Blockly.DRAG_FREE; + } + e.stopPropagation(); + e.preventDefault(); + } +}; + +/** + * Handle a key-down on SVG drawing surface. + * @param {!Event} e Key down event. + * @private + */ +Blockly.onKeyDown_ = function(e) { + if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) { + // No key actions on readonly workspaces. + // When focused on an HTML text input widget, don't trap any keys. + return; + } + var deleteBlock = false; + if (e.keyCode == 27) { + // Pressing esc closes the context menu. + Blockly.hideChaff(); + } else if (e.keyCode == 8 || e.keyCode == 46) { + // Delete or backspace. + // Stop the browser from going back to the previous page. + // Do this first to prevent an error in the delete code from resulting in + // data loss. + e.preventDefault(); + if (Blockly.selected && Blockly.selected.isDeletable()) { + deleteBlock = true; + } + } else if (e.altKey || e.ctrlKey || e.metaKey) { + if (Blockly.selected && + Blockly.selected.isDeletable() && Blockly.selected.isMovable()) { + if (e.keyCode == 67) { + // 'c' for copy. + Blockly.hideChaff(); + Blockly.copy_(Blockly.selected); + } else if (e.keyCode == 88) { + // 'x' for cut. + Blockly.copy_(Blockly.selected); + deleteBlock = true; + } + } + if (e.keyCode == 86) { + // 'v' for paste. + if (Blockly.clipboardXml_) { + Blockly.Events.setGroup(true); + Blockly.clipboardSource_.paste(Blockly.clipboardXml_); + Blockly.Events.setGroup(false); + } + } else if (e.keyCode == 90) { + // 'z' for undo 'Z' is for redo. + Blockly.hideChaff(); + Blockly.mainWorkspace.undo(e.shiftKey); + } + } + if (deleteBlock) { + // Common code for delete and cut. + Blockly.Events.setGroup(true); + Blockly.hideChaff(); + var heal = Blockly.dragMode_ != Blockly.DRAG_FREE; + Blockly.selected.dispose(heal, true); + if (Blockly.highlightedConnection_) { + Blockly.highlightedConnection_.unhighlight(); + Blockly.highlightedConnection_ = null; + } + Blockly.Events.setGroup(false); + } +}; + +/** + * Stop binding to the global mouseup and mousemove events. + * @private + */ +Blockly.terminateDrag_ = function() { + Blockly.BlockSvg.terminateDrag(); + Blockly.Flyout.terminateDrag_(); +}; + +/** + * PID of queued long-press task. + * @private + */ +Blockly.longPid_ = 0; + +/** + * Context menus on touch devices are activated using a long-press. + * Unfortunately the contextmenu touch event is currently (2015) only suported + * by Chrome. This function is fired on any touchstart event, queues a task, + * which after about a second opens the context menu. The tasks is killed + * if the touch event terminates early. + * @param {!Event} e Touch start event. + * @param {!Blockly.Block|!Blockly.WorkspaceSvg} uiObject The block or workspace + * under the touchstart event. + * @private + */ +Blockly.longStart_ = function(e, uiObject) { + Blockly.longStop_(); + Blockly.longPid_ = setTimeout(function() { + e.button = 2; // Simulate a right button click. + uiObject.onMouseDown_(e); + }, Blockly.LONGPRESS); +}; + +/** + * Nope, that's not a long-press. Either touchend or touchcancel was fired, + * or a drag hath begun. Kill the queued long-press task. + * @private + */ +Blockly.longStop_ = function() { + if (Blockly.longPid_) { + clearTimeout(Blockly.longPid_); + Blockly.longPid_ = 0; + } +}; + +/** + * Copy a block onto the local clipboard. + * @param {!Blockly.Block} block Block to be copied. + * @private + */ +Blockly.copy_ = function(block) { + var xmlBlock = Blockly.Xml.blockToDom(block); + if (Blockly.dragMode_ != Blockly.DRAG_FREE) { + Blockly.Xml.deleteNext(xmlBlock); + } + // Encode start position in XML. + var xy = block.getRelativeToSurfaceXY(); + xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x); + xmlBlock.setAttribute('y', xy.y); + Blockly.clipboardXml_ = xmlBlock; + Blockly.clipboardSource_ = block.workspace; +}; + +/** + * Duplicate this block and its children. + * @param {!Blockly.Block} block Block to be copied. + * @private + */ +Blockly.duplicate_ = function(block) { + // Save the clipboard. + var clipboardXml = Blockly.clipboardXml_; + var clipboardSource = Blockly.clipboardSource_; + + // Create a duplicate via a copy/paste operation. + Blockly.copy_(block); + block.workspace.paste(Blockly.clipboardXml_); + + // Restore the clipboard. + Blockly.clipboardXml_ = clipboardXml; + Blockly.clipboardSource_ = clipboardSource; +}; + +/** + * Cancel the native context menu, unless the focus is on an HTML input widget. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.onContextMenu_ = function(e) { + if (!Blockly.isTargetInput_(e)) { + // When focused on an HTML text input widget, don't cancel the context menu. + e.preventDefault(); + } +}; + +/** + * Close tooltips, context menus, dropdown selections, etc. + * @param {boolean=} opt_allowToolbox If true, don't close the toolbox. + */ +Blockly.hideChaff = function(opt_allowToolbox) { + Blockly.Tooltip.hide(); + Blockly.WidgetDiv.hide(); + if (!opt_allowToolbox) { + var workspace = Blockly.getMainWorkspace(); + if (workspace.toolbox_ && + workspace.toolbox_.flyout_ && + workspace.toolbox_.flyout_.autoClose) { + workspace.toolbox_.clearSelection(); + } + } +}; + +/** + * When something in Blockly's workspace changes, call a function. + * @param {!Function} func Function to call. + * @return {!Array.} Opaque data that can be passed to + * removeChangeListener. + * @deprecated April 2015 + */ +Blockly.addChangeListener = function(func) { + // Backwards compatability from before there could be multiple workspaces. + console.warn('Deprecated call to Blockly.addChangeListener, ' + + 'use workspace.addChangeListener instead.'); + return Blockly.getMainWorkspace().addChangeListener(func); +}; + +/** + * Returns the main workspace. Returns the last used main workspace (based on + * focus). Try not to use this function, particularly if there are multiple + * Blockly instances on a page. + * @return {!Blockly.Workspace} The main workspace. + */ +Blockly.getMainWorkspace = function() { + return Blockly.mainWorkspace; +}; + +// IE9 does not have a console. Create a stub to stop errors. +if (!goog.global['console']) { + goog.global['console'] = { + 'log': function() {}, + 'warn': function() {} + }; +} + +// Export symbols that would otherwise be renamed by Closure compiler. +if (!goog.global['Blockly']) { + goog.global['Blockly'] = {}; +} +goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace; +goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener; diff --git a/src/blockly/core/blocks.js b/src/blockly/core/blocks.js new file mode 100644 index 0000000..d6932ce --- /dev/null +++ b/src/blockly/core/blocks.js @@ -0,0 +1,33 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Empty name space for the Blocks singleton. + * @author spertus@google.com (Ellen Spertus) + */ +'use strict'; + +goog.provide('Blockly.Blocks'); + +/** + * Allow for switching between one and zero based indexing for lists and text, + * one based by default. + */ +Blockly.Blocks.ONE_BASED_INDEXING = true; diff --git a/src/blockly/core/bubble.js b/src/blockly/core/bubble.js new file mode 100644 index 0000000..d4c1e27 --- /dev/null +++ b/src/blockly/core/bubble.js @@ -0,0 +1,579 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing a UI bubble. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Bubble'); + +goog.require('Blockly.Workspace'); +goog.require('goog.dom'); +goog.require('goog.math'); +goog.require('goog.math.Coordinate'); +goog.require('goog.userAgent'); + + +/** + * Class for UI bubble. + * @param {!Blockly.WorkspaceSvg} workspace The workspace on which to draw the + * bubble. + * @param {!Element} content SVG content for the bubble. + * @param {Element} shape SVG element to avoid eclipsing. + * @param {!goog.math.Coodinate} anchorXY Absolute position of bubble's anchor + * point. + * @param {?number} bubbleWidth Width of bubble, or null if not resizable. + * @param {?number} bubbleHeight Height of bubble, or null if not resizable. + * @constructor + */ +Blockly.Bubble = function(workspace, content, shape, anchorXY, + bubbleWidth, bubbleHeight) { + this.workspace_ = workspace; + this.content_ = content; + this.shape_ = shape; + + var angle = Blockly.Bubble.ARROW_ANGLE; + if (this.workspace_.RTL) { + angle = -angle; + } + this.arrow_radians_ = goog.math.toRadians(angle); + + var canvas = workspace.getBubbleCanvas(); + canvas.appendChild(this.createDom_(content, !!(bubbleWidth && bubbleHeight))); + + this.setAnchorLocation(anchorXY); + if (!bubbleWidth || !bubbleHeight) { + var bBox = /** @type {SVGLocatable} */ (this.content_).getBBox(); + bubbleWidth = bBox.width + 2 * Blockly.Bubble.BORDER_WIDTH; + bubbleHeight = bBox.height + 2 * Blockly.Bubble.BORDER_WIDTH; + } + this.setBubbleSize(bubbleWidth, bubbleHeight); + + // Render the bubble. + this.positionBubble_(); + this.renderArrow_(); + this.rendered_ = true; + + if (!workspace.options.readOnly) { + Blockly.bindEvent_(this.bubbleBack_, 'mousedown', this, + this.bubbleMouseDown_); + if (this.resizeGroup_) { + Blockly.bindEvent_(this.resizeGroup_, 'mousedown', this, + this.resizeMouseDown_); + } + } +}; + +/** + * Width of the border around the bubble. + */ +Blockly.Bubble.BORDER_WIDTH = 6; + +/** + * Determines the thickness of the base of the arrow in relation to the size + * of the bubble. Higher numbers result in thinner arrows. + */ +Blockly.Bubble.ARROW_THICKNESS = 10; + +/** + * The number of degrees that the arrow bends counter-clockwise. + */ +Blockly.Bubble.ARROW_ANGLE = 20; + +/** + * The sharpness of the arrow's bend. Higher numbers result in smoother arrows. + */ +Blockly.Bubble.ARROW_BEND = 4; + +/** + * Distance between arrow point and anchor point. + */ +Blockly.Bubble.ANCHOR_RADIUS = 8; + +/** + * Wrapper function called when a mouseUp occurs during a drag operation. + * @type {Array.} + * @private + */ +Blockly.Bubble.onMouseUpWrapper_ = null; + +/** + * Wrapper function called when a mouseMove occurs during a drag operation. + * @type {Array.} + * @private + */ +Blockly.Bubble.onMouseMoveWrapper_ = null; + +/** + * Function to call on resize of bubble. + * @type {Function} + */ +Blockly.Bubble.prototype.resizeCallback_ = null; + +/** + * Stop binding to the global mouseup and mousemove events. + * @private + */ +Blockly.Bubble.unbindDragEvents_ = function() { + if (Blockly.Bubble.onMouseUpWrapper_) { + Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_); + Blockly.Bubble.onMouseUpWrapper_ = null; + } + if (Blockly.Bubble.onMouseMoveWrapper_) { + Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_); + Blockly.Bubble.onMouseMoveWrapper_ = null; + } +}; + +/** + * Flag to stop incremental rendering during construction. + * @private + */ +Blockly.Bubble.prototype.rendered_ = false; + +/** + * Absolute coordinate of anchor point. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.Bubble.prototype.anchorXY_ = null; + +/** + * Relative X coordinate of bubble with respect to the anchor's centre. + * In RTL mode the initial value is negated. + * @private + */ +Blockly.Bubble.prototype.relativeLeft_ = 0; + +/** + * Relative Y coordinate of bubble with respect to the anchor's centre. + * @private + */ +Blockly.Bubble.prototype.relativeTop_ = 0; + +/** + * Width of bubble. + * @private + */ +Blockly.Bubble.prototype.width_ = 0; + +/** + * Height of bubble. + * @private + */ +Blockly.Bubble.prototype.height_ = 0; + +/** + * Automatically position and reposition the bubble. + * @private + */ +Blockly.Bubble.prototype.autoLayout_ = true; + +/** + * Create the bubble's DOM. + * @param {!Element} content SVG content for the bubble. + * @param {boolean} hasResize Add diagonal resize gripper if true. + * @return {!Element} The bubble's SVG group. + * @private + */ +Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { + /* Create the bubble. Here's the markup that will be generated: + + + + + + + + + + + [...content goes here...] + + */ + this.bubbleGroup_ = Blockly.createSvgElement('g', {}, null); + var filter = + {'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'}; + if (goog.userAgent.getUserAgentString().indexOf('JavaFX') != -1) { + // Multiple reports that JavaFX can't handle filters. UserAgent: + // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44 + // (KHTML, like Gecko) JavaFX/8.0 Safari/537.44 + // https://github.com/google/blockly/issues/99 + filter = {}; + } + var bubbleEmboss = Blockly.createSvgElement('g', + filter, this.bubbleGroup_); + this.bubbleArrow_ = Blockly.createSvgElement('path', {}, bubbleEmboss); + this.bubbleBack_ = Blockly.createSvgElement('rect', + {'class': 'blocklyDraggable', 'x': 0, 'y': 0, + 'rx': Blockly.Bubble.BORDER_WIDTH, 'ry': Blockly.Bubble.BORDER_WIDTH}, + bubbleEmboss); + if (hasResize) { + this.resizeGroup_ = Blockly.createSvgElement('g', + {'class': this.workspace_.RTL ? + 'blocklyResizeSW' : 'blocklyResizeSE'}, + this.bubbleGroup_); + var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH; + Blockly.createSvgElement('polygon', + {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())}, + this.resizeGroup_); + Blockly.createSvgElement('line', + {'class': 'blocklyResizeLine', + 'x1': resizeSize / 3, 'y1': resizeSize - 1, + 'x2': resizeSize - 1, 'y2': resizeSize / 3}, this.resizeGroup_); + Blockly.createSvgElement('line', + {'class': 'blocklyResizeLine', + 'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1, + 'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3}, this.resizeGroup_); + } else { + this.resizeGroup_ = null; + } + this.bubbleGroup_.appendChild(content); + return this.bubbleGroup_; +}; + +/** + * Handle a mouse-down on bubble's border. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) { + this.promote_(); + Blockly.Bubble.unbindDragEvents_(); + if (Blockly.isRightButton(e)) { + // No right-click. + e.stopPropagation(); + return; + } else if (Blockly.isTargetInput_(e)) { + // When focused on an HTML text input widget, don't trap any events. + return; + } + // Left-click (or middle click) + Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); + + this.workspace_.startDrag(e, new goog.math.Coordinate( + this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_, + this.relativeTop_)); + + Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document, + 'mouseup', this, Blockly.Bubble.unbindDragEvents_); + Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document, + 'mousemove', this, this.bubbleMouseMove_); + Blockly.hideChaff(); + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); +}; + +/** + * Drag this bubble to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ +Blockly.Bubble.prototype.bubbleMouseMove_ = function(e) { + this.autoLayout_ = false; + var newXY = this.workspace_.moveDrag(e); + this.relativeLeft_ = this.workspace_.RTL ? -newXY.x : newXY.x; + this.relativeTop_ = newXY.y; + this.positionBubble_(); + this.renderArrow_(); +}; + +/** + * Handle a mouse-down on bubble's resize corner. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Bubble.prototype.resizeMouseDown_ = function(e) { + this.promote_(); + Blockly.Bubble.unbindDragEvents_(); + if (Blockly.isRightButton(e)) { + // No right-click. + e.stopPropagation(); + return; + } + // Left-click (or middle click) + Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); + + this.workspace_.startDrag(e, new goog.math.Coordinate( + this.workspace_.RTL ? -this.width_ : this.width_, this.height_)); + + Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document, + 'mouseup', this, Blockly.Bubble.unbindDragEvents_); + Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document, + 'mousemove', this, this.resizeMouseMove_); + Blockly.hideChaff(); + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); +}; + +/** + * Resize this bubble to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ +Blockly.Bubble.prototype.resizeMouseMove_ = function(e) { + this.autoLayout_ = false; + var newXY = this.workspace_.moveDrag(e); + this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y); + if (this.workspace_.RTL) { + // RTL requires the bubble to move its left edge. + this.positionBubble_(); + } +}; + +/** + * Register a function as a callback event for when the bubble is resized. + * @param {!Function} callback The function to call on resize. + */ +Blockly.Bubble.prototype.registerResizeEvent = function(callback) { + this.resizeCallback_ = callback; +}; + +/** + * Move this bubble to the top of the stack. + * @private + */ +Blockly.Bubble.prototype.promote_ = function() { + var svgGroup = this.bubbleGroup_.parentNode; + svgGroup.appendChild(this.bubbleGroup_); +}; + +/** + * Notification that the anchor has moved. + * Update the arrow and bubble accordingly. + * @param {!goog.math.Coordinate} xy Absolute location. + */ +Blockly.Bubble.prototype.setAnchorLocation = function(xy) { + this.anchorXY_ = xy; + if (this.rendered_) { + this.positionBubble_(); + } +}; + +/** + * Position the bubble so that it does not fall off-screen. + * @private + */ +Blockly.Bubble.prototype.layoutBubble_ = function() { + // Compute the preferred bubble location. + var relativeLeft = -this.width_ / 4; + var relativeTop = -this.height_ - Blockly.BlockSvg.MIN_BLOCK_Y; + // Prevent the bubble from being off-screen. + var metrics = this.workspace_.getMetrics(); + metrics.viewWidth /= this.workspace_.scale; + metrics.viewLeft /= this.workspace_.scale; + var anchorX = this.anchorXY_.x; + if (this.workspace_.RTL) { + if (anchorX - metrics.viewLeft - relativeLeft - this.width_ < + Blockly.Scrollbar.scrollbarThickness) { + // Slide the bubble right until it is onscreen. + relativeLeft = anchorX - metrics.viewLeft - this.width_ - + Blockly.Scrollbar.scrollbarThickness; + } else if (anchorX - metrics.viewLeft - relativeLeft > + metrics.viewWidth) { + // Slide the bubble left until it is onscreen. + relativeLeft = anchorX - metrics.viewLeft - metrics.viewWidth; + } + } else { + if (anchorX + relativeLeft < metrics.viewLeft) { + // Slide the bubble right until it is onscreen. + relativeLeft = metrics.viewLeft - anchorX; + } else if (metrics.viewLeft + metrics.viewWidth < + anchorX + relativeLeft + this.width_ + + Blockly.BlockSvg.SEP_SPACE_X + + Blockly.Scrollbar.scrollbarThickness) { + // Slide the bubble left until it is onscreen. + relativeLeft = metrics.viewLeft + metrics.viewWidth - anchorX - + this.width_ - Blockly.Scrollbar.scrollbarThickness; + } + } + if (this.anchorXY_.y + relativeTop < metrics.viewTop) { + // Slide the bubble below the block. + var bBox = /** @type {SVGLocatable} */ (this.shape_).getBBox(); + relativeTop = bBox.height; + } + this.relativeLeft_ = relativeLeft; + this.relativeTop_ = relativeTop; +}; + +/** + * Move the bubble to a location relative to the anchor's centre. + * @private + */ +Blockly.Bubble.prototype.positionBubble_ = function() { + var left = this.anchorXY_.x; + if (this.workspace_.RTL) { + left -= this.relativeLeft_ + this.width_; + } else { + left += this.relativeLeft_; + } + var top = this.relativeTop_ + this.anchorXY_.y; + this.bubbleGroup_.setAttribute('transform', + 'translate(' + left + ',' + top + ')'); +}; + +/** + * Get the dimensions of this bubble. + * @return {!Object} Object with width and height properties. + */ +Blockly.Bubble.prototype.getBubbleSize = function() { + return {width: this.width_, height: this.height_}; +}; + +/** + * Size this bubble. + * @param {number} width Width of the bubble. + * @param {number} height Height of the bubble. + */ +Blockly.Bubble.prototype.setBubbleSize = function(width, height) { + var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH; + // Minimum size of a bubble. + width = Math.max(width, doubleBorderWidth + 45); + height = Math.max(height, doubleBorderWidth + 20); + this.width_ = width; + this.height_ = height; + this.bubbleBack_.setAttribute('width', width); + this.bubbleBack_.setAttribute('height', height); + if (this.resizeGroup_) { + if (this.workspace_.RTL) { + // Mirror the resize group. + var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH; + this.resizeGroup_.setAttribute('transform', 'translate(' + + resizeSize + ',' + (height - doubleBorderWidth) + ') scale(-1 1)'); + } else { + this.resizeGroup_.setAttribute('transform', 'translate(' + + (width - doubleBorderWidth) + ',' + + (height - doubleBorderWidth) + ')'); + } + } + if (this.rendered_) { + if (this.autoLayout_) { + this.layoutBubble_(); + } + this.positionBubble_(); + this.renderArrow_(); + } + // Allow the contents to resize. + if (this.resizeCallback_) { + this.resizeCallback_(); + } +}; + +/** + * Draw the arrow between the bubble and the origin. + * @private + */ +Blockly.Bubble.prototype.renderArrow_ = function() { + var steps = []; + // Find the relative coordinates of the center of the bubble. + var relBubbleX = this.width_ / 2; + var relBubbleY = this.height_ / 2; + // Find the relative coordinates of the center of the anchor. + var relAnchorX = -this.relativeLeft_; + var relAnchorY = -this.relativeTop_; + if (relBubbleX == relAnchorX && relBubbleY == relAnchorY) { + // Null case. Bubble is directly on top of the anchor. + // Short circuit this rather than wade through divide by zeros. + steps.push('M ' + relBubbleX + ',' + relBubbleY); + } else { + // Compute the angle of the arrow's line. + var rise = relAnchorY - relBubbleY; + var run = relAnchorX - relBubbleX; + if (this.workspace_.RTL) { + run *= -1; + } + var hypotenuse = Math.sqrt(rise * rise + run * run); + var angle = Math.acos(run / hypotenuse); + if (rise < 0) { + angle = 2 * Math.PI - angle; + } + // Compute a line perpendicular to the arrow. + var rightAngle = angle + Math.PI / 2; + if (rightAngle > Math.PI * 2) { + rightAngle -= Math.PI * 2; + } + var rightRise = Math.sin(rightAngle); + var rightRun = Math.cos(rightAngle); + + // Calculate the thickness of the base of the arrow. + var bubbleSize = this.getBubbleSize(); + var thickness = (bubbleSize.width + bubbleSize.height) / + Blockly.Bubble.ARROW_THICKNESS; + thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 2; + + // Back the tip of the arrow off of the anchor. + var backoffRatio = 1 - Blockly.Bubble.ANCHOR_RADIUS / hypotenuse; + relAnchorX = relBubbleX + backoffRatio * run; + relAnchorY = relBubbleY + backoffRatio * rise; + + // Coordinates for the base of the arrow. + var baseX1 = relBubbleX + thickness * rightRun; + var baseY1 = relBubbleY + thickness * rightRise; + var baseX2 = relBubbleX - thickness * rightRun; + var baseY2 = relBubbleY - thickness * rightRise; + + // Distortion to curve the arrow. + var swirlAngle = angle + this.arrow_radians_; + if (swirlAngle > Math.PI * 2) { + swirlAngle -= Math.PI * 2; + } + var swirlRise = Math.sin(swirlAngle) * + hypotenuse / Blockly.Bubble.ARROW_BEND; + var swirlRun = Math.cos(swirlAngle) * + hypotenuse / Blockly.Bubble.ARROW_BEND; + + steps.push('M' + baseX1 + ',' + baseY1); + steps.push('C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) + + ' ' + relAnchorX + ',' + relAnchorY + + ' ' + relAnchorX + ',' + relAnchorY); + steps.push('C' + relAnchorX + ',' + relAnchorY + + ' ' + (baseX2 + swirlRun) + ',' + (baseY2 + swirlRise) + + ' ' + baseX2 + ',' + baseY2); + } + steps.push('z'); + this.bubbleArrow_.setAttribute('d', steps.join(' ')); +}; + +/** + * Change the colour of a bubble. + * @param {string} hexColour Hex code of colour. + */ +Blockly.Bubble.prototype.setColour = function(hexColour) { + this.bubbleBack_.setAttribute('fill', hexColour); + this.bubbleArrow_.setAttribute('fill', hexColour); +}; + +/** + * Dispose of this bubble. + */ +Blockly.Bubble.prototype.dispose = function() { + Blockly.Bubble.unbindDragEvents_(); + // Dispose of and unlink the bubble. + goog.dom.removeNode(this.bubbleGroup_); + this.bubbleGroup_ = null; + this.bubbleArrow_ = null; + this.bubbleBack_ = null; + this.resizeGroup_ = null; + this.workspace_ = null; + this.content_ = null; + this.shape_ = null; +}; diff --git a/src/blockly/core/comment.js b/src/blockly/core/comment.js new file mode 100644 index 0000000..2121530 --- /dev/null +++ b/src/blockly/core/comment.js @@ -0,0 +1,278 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing a code comment. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Comment'); + +goog.require('Blockly.Bubble'); +goog.require('Blockly.Icon'); +goog.require('goog.userAgent'); + + +/** + * Class for a comment. + * @param {!Blockly.Block} block The block associated with this comment. + * @extends {Blockly.Icon} + * @constructor + */ +Blockly.Comment = function(block) { + Blockly.Comment.superClass_.constructor.call(this, block); + this.createIcon(); +}; +goog.inherits(Blockly.Comment, Blockly.Icon); + +/** + * Comment text (if bubble is not visible). + * @private + */ +Blockly.Comment.prototype.text_ = ''; + +/** + * Width of bubble. + * @private + */ +Blockly.Comment.prototype.width_ = 160; + +/** + * Height of bubble. + * @private + */ +Blockly.Comment.prototype.height_ = 80; + +/** + * Draw the comment icon. + * @param {!Element} group The icon group. + * @private + */ +Blockly.Comment.prototype.drawIcon_ = function(group) { + // Circle. + Blockly.createSvgElement('circle', + {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'}, + group); + // Can't use a real '?' text character since different browsers and operating + // systems render it differently. + // Body of question mark. + Blockly.createSvgElement('path', + {'class': 'blocklyIconSymbol', + 'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405 0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25 -1.201,0.998 -1.201,1.528 -1.204,2.19z'}, + group); + // Dot of question point. + Blockly.createSvgElement('rect', + {'class': 'blocklyIconSymbol', + 'x': '6.8', 'y': '10.78', 'height': '2', 'width': '2'}, + group); +}; + +/** + * Create the editor for the comment's bubble. + * @return {!Element} The top-level node of the editor. + * @private + */ +Blockly.Comment.prototype.createEditor_ = function() { + /* Create the editor. Here's the markup that will be generated: + + + + + + */ + this.foreignObject_ = Blockly.createSvgElement('foreignObject', + {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH}, + null); + var body = document.createElementNS(Blockly.HTML_NS, 'body'); + body.setAttribute('xmlns', Blockly.HTML_NS); + body.className = 'blocklyMinimalBody'; + var textarea = document.createElementNS(Blockly.HTML_NS, 'textarea'); + textarea.className = 'blocklyCommentTextarea'; + textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR'); + body.appendChild(textarea); + this.textarea_ = textarea; + this.foreignObject_.appendChild(body); + Blockly.bindEvent_(textarea, 'mouseup', this, this.textareaFocus_); + // Don't zoom with mousewheel. + Blockly.bindEvent_(textarea, 'wheel', this, function(e) { + e.stopPropagation(); + }); + Blockly.bindEvent_(textarea, 'change', this, function(e) { + if (this.text_ != textarea.value) { + Blockly.Events.fire(new Blockly.Events.Change( + this.block_, 'comment', null, this.text_, textarea.value)); + this.text_ = textarea.value; + } + }); + setTimeout(function() { + textarea.focus(); + }, 0); + return this.foreignObject_; +}; + +/** + * Add or remove editability of the comment. + * @override + */ +Blockly.Comment.prototype.updateEditable = function() { + if (this.isVisible()) { + // Toggling visibility will force a rerendering. + this.setVisible(false); + this.setVisible(true); + } + // Allow the icon to update. + Blockly.Icon.prototype.updateEditable.call(this); +}; + +/** + * Callback function triggered when the bubble has resized. + * Resize the text area accordingly. + * @private + */ +Blockly.Comment.prototype.resizeBubble_ = function() { + if (this.isVisible()) { + var size = this.bubble_.getBubbleSize(); + var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH; + this.foreignObject_.setAttribute('width', size.width - doubleBorderWidth); + this.foreignObject_.setAttribute('height', size.height - doubleBorderWidth); + this.textarea_.style.width = (size.width - doubleBorderWidth - 4) + 'px'; + this.textarea_.style.height = (size.height - doubleBorderWidth - 4) + 'px'; + } +}; + +/** + * Show or hide the comment bubble. + * @param {boolean} visible True if the bubble should be visible. + */ +Blockly.Comment.prototype.setVisible = function(visible) { + if (visible == this.isVisible()) { + // No change. + return; + } + Blockly.Events.fire( + new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible)); + if ((!this.block_.isEditable() && !this.textarea_) || goog.userAgent.IE) { + // Steal the code from warnings to make an uneditable text bubble. + // MSIE does not support foreignobject; textareas are impossible. + // http://msdn.microsoft.com/en-us/library/hh834675%28v=vs.85%29.aspx + // Always treat comments in IE as uneditable. + Blockly.Warning.prototype.setVisible.call(this, visible); + return; + } + // Save the bubble stats before the visibility switch. + var text = this.getText(); + var size = this.getBubbleSize(); + if (visible) { + // Create the bubble. + this.bubble_ = new Blockly.Bubble( + /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace), + this.createEditor_(), this.block_.svgPath_, + this.iconXY_, this.width_, this.height_); + this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this)); + this.updateColour(); + } else { + // Dispose of the bubble. + this.bubble_.dispose(); + this.bubble_ = null; + this.textarea_ = null; + this.foreignObject_ = null; + } + // Restore the bubble stats after the visibility switch. + this.setText(text); + this.setBubbleSize(size.width, size.height); +}; + +/** + * Bring the comment to the top of the stack when clicked on. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.Comment.prototype.textareaFocus_ = function(e) { + // Ideally this would be hooked to the focus event for the comment. + // However doing so in Firefox swallows the cursor for unknown reasons. + // So this is hooked to mouseup instead. No big deal. + this.bubble_.promote_(); + // Since the act of moving this node within the DOM causes a loss of focus, + // we need to reapply the focus. + this.textarea_.focus(); +}; + +/** + * Get the dimensions of this comment's bubble. + * @return {!Object} Object with width and height properties. + */ +Blockly.Comment.prototype.getBubbleSize = function() { + if (this.isVisible()) { + return this.bubble_.getBubbleSize(); + } else { + return {width: this.width_, height: this.height_}; + } +}; + +/** + * Size this comment's bubble. + * @param {number} width Width of the bubble. + * @param {number} height Height of the bubble. + */ +Blockly.Comment.prototype.setBubbleSize = function(width, height) { + if (this.textarea_) { + this.bubble_.setBubbleSize(width, height); + } else { + this.width_ = width; + this.height_ = height; + } +}; + +/** + * Returns this comment's text. + * @return {string} Comment text. + */ +Blockly.Comment.prototype.getText = function() { + return this.textarea_ ? this.textarea_.value : this.text_; +}; + +/** + * Set this comment's text. + * @param {string} text Comment text. + */ +Blockly.Comment.prototype.setText = function(text) { + if (this.text_ != text) { + Blockly.Events.fire(new Blockly.Events.Change( + this.block_, 'comment', null, this.text_, text)); + this.text_ = text; + } + if (this.textarea_) { + this.textarea_.value = text; + } +}; + +/** + * Dispose of this comment. + */ +Blockly.Comment.prototype.dispose = function() { + if (Blockly.Events.isEnabled()) { + this.setText(''); // Fire event to delete comment. + } + this.block_.comment = null; + Blockly.Icon.prototype.dispose.call(this); +}; diff --git a/src/blockly/core/connection.js b/src/blockly/core/connection.js new file mode 100644 index 0000000..3b8c82a --- /dev/null +++ b/src/blockly/core/connection.js @@ -0,0 +1,615 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Components for creating connections between blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Connection'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); + + +/** + * Class for a connection between blocks. + * @param {!Blockly.Block} source The block establishing this connection. + * @param {number} type The type of the connection. + * @constructor + */ +Blockly.Connection = function(source, type) { + /** + * @type {!Blockly.Block} + * @private + */ + this.sourceBlock_ = source; + /** @type {number} */ + this.type = type; + // Shortcut for the databases for this connection's workspace. + if (source.workspace.connectionDBList) { + this.db_ = source.workspace.connectionDBList[type]; + this.dbOpposite_ = + source.workspace.connectionDBList[Blockly.OPPOSITE_TYPE[type]]; + this.hidden_ = !this.db_; + } +}; + +/** + * Constants for checking whether two connections are compatible. + */ +Blockly.Connection.CAN_CONNECT = 0; +Blockly.Connection.REASON_SELF_CONNECTION = 1; +Blockly.Connection.REASON_WRONG_TYPE = 2; +Blockly.Connection.REASON_TARGET_NULL = 3; +Blockly.Connection.REASON_CHECKS_FAILED = 4; +Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5; +Blockly.Connection.REASON_SHADOW_PARENT = 6; + +/** + * Connection this connection connects to. Null if not connected. + * @type {Blockly.Connection} + */ +Blockly.Connection.prototype.targetConnection = null; + +/** + * List of compatible value types. Null if all types are compatible. + * @type {Array} + * @private + */ +Blockly.Connection.prototype.check_ = null; + +/** + * DOM representation of a shadow block, or null if none. + * @type {Element} + * @private + */ +Blockly.Connection.prototype.shadowDom_ = null; + +/** + * Horizontal location of this connection. + * @type {number} + * @private + */ +Blockly.Connection.prototype.x_ = 0; + +/** + * Vertical location of this connection. + * @type {number} + * @private + */ +Blockly.Connection.prototype.y_ = 0; + +/** + * Has this connection been added to the connection database? + * @type {boolean} + * @private + */ +Blockly.Connection.prototype.inDB_ = false; + +/** + * Connection database for connections of this type on the current workspace. + * @type {Blockly.ConnectionDB} + * @private + */ +Blockly.Connection.prototype.db_ = null; + +/** + * Connection database for connections compatible with this type on the + * current workspace. + * @type {Blockly.ConnectionDB} + * @private + */ +Blockly.Connection.prototype.dbOpposite_ = null; + +/** + * Whether this connections is hidden (not tracked in a database) or not. + * @type {boolean} + * @private + */ +Blockly.Connection.prototype.hidden_ = null; + +/** + * Connect two connections together. This is the connection on the superior + * block. + * @param {!Blockly.Connection} childConnection Connection on inferior block. + * @private + */ +Blockly.Connection.prototype.connect_ = function(childConnection) { + var parentConnection = this; + var parentBlock = parentConnection.getSourceBlock(); + var childBlock = childConnection.getSourceBlock(); + // Disconnect any existing parent on the child connection. + if (childConnection.isConnected()) { + childConnection.disconnect(); + } + if (parentConnection.isConnected()) { + // Other connection is already connected to something. + // Disconnect it and reattach it or bump it as needed. + var orphanBlock = parentConnection.targetBlock(); + var shadowDom = parentConnection.getShadowDom(); + // Temporarily set the shadow DOM to null so it does not respawn. + parentConnection.setShadowDom(null); + // Displaced shadow blocks dissolve rather than reattaching or bumping. + if (orphanBlock.isShadow()) { + // Save the shadow block so that field values are preserved. + shadowDom = Blockly.Xml.blockToDom(orphanBlock); + orphanBlock.dispose(); + orphanBlock = null; + } else if (parentConnection.type == Blockly.INPUT_VALUE) { + // Value connections. + // If female block is already connected, disconnect and bump the male. + if (!orphanBlock.outputConnection) { + throw 'Orphan block does not have an output connection.'; + } + // Attempt to reattach the orphan at the end of the newly inserted + // block. Since this block may be a row, walk down to the end + // or to the first (and only) shadow block. + var connection = Blockly.Connection.lastConnectionInRow_( + childBlock, orphanBlock); + if (connection) { + orphanBlock.outputConnection.connect(connection); + orphanBlock = null; + } + } else if (parentConnection.type == Blockly.NEXT_STATEMENT) { + // Statement connections. + // Statement blocks may be inserted into the middle of a stack. + // Split the stack. + if (!orphanBlock.previousConnection) { + throw 'Orphan block does not have a previous connection.'; + } + // Attempt to reattach the orphan at the bottom of the newly inserted + // block. Since this block may be a stack, walk down to the end. + var newBlock = childBlock; + while (newBlock.nextConnection) { + var nextBlock = newBlock.getNextBlock(); + if (nextBlock && !nextBlock.isShadow()) { + newBlock = nextBlock; + } else { + if (orphanBlock.previousConnection.checkType_( + newBlock.nextConnection)) { + newBlock.nextConnection.connect(orphanBlock.previousConnection); + orphanBlock = null; + } + break; + } + } + } + if (orphanBlock) { + // Unable to reattach orphan. + parentConnection.disconnect(); + if (Blockly.Events.recordUndo) { + // Bump it off to the side after a moment. + var group = Blockly.Events.getGroup(); + setTimeout(function() { + // Verify orphan hasn't been deleted or reconnected (user on meth). + if (orphanBlock.workspace && !orphanBlock.getParent()) { + Blockly.Events.setGroup(group); + if (orphanBlock.outputConnection) { + orphanBlock.outputConnection.bumpAwayFrom_(parentConnection); + } else if (orphanBlock.previousConnection) { + orphanBlock.previousConnection.bumpAwayFrom_(parentConnection); + } + Blockly.Events.setGroup(false); + } + }, Blockly.BUMP_DELAY); + } + } + // Restore the shadow DOM. + parentConnection.setShadowDom(shadowDom); + } + + var event; + if (Blockly.Events.isEnabled()) { + event = new Blockly.Events.Move(childBlock); + } + // Establish the connections. + Blockly.Connection.connectReciprocally_(parentConnection, childConnection); + // Demote the inferior block so that one is a child of the superior one. + childBlock.setParent(parentBlock); + if (event) { + event.recordNew(); + Blockly.Events.fire(event); + } +}; + +/** + * Sever all links to this connection (not including from the source object). + */ +Blockly.Connection.prototype.dispose = function() { + if (this.isConnected()) { + throw 'Disconnect connection before disposing of it.'; + } + if (this.inDB_) { + this.db_.removeConnection_(this); + } + if (Blockly.highlightedConnection_ == this) { + Blockly.highlightedConnection_ = null; + } + if (Blockly.localConnection_ == this) { + Blockly.localConnection_ = null; + } + this.db_ = null; + this.dbOpposite_ = null; +}; + +/** + * Get the source block for this connection. + * @return {Blockly.Block} The source block, or null if there is none. + */ +Blockly.Connection.prototype.getSourceBlock = function() { + return this.sourceBlock_; +}; + +/** + * Does the connection belong to a superior block (higher in the source stack)? + * @return {boolean} True if connection faces down or right. + */ +Blockly.Connection.prototype.isSuperior = function() { + return this.type == Blockly.INPUT_VALUE || + this.type == Blockly.NEXT_STATEMENT; +}; + +/** + * Is the connection connected? + * @return {boolean} True if connection is connected to another connection. + */ +Blockly.Connection.prototype.isConnected = function() { + return !!this.targetConnection; +}; + +/** + * Checks whether the current connection can connect with the target + * connection. + * @param {Blockly.Connection} target Connection to check compatibility with. + * @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal, + * an error code otherwise. + * @private + */ +Blockly.Connection.prototype.canConnectWithReason_ = function(target) { + if (!target) { + return Blockly.Connection.REASON_TARGET_NULL; + } + if (this.isSuperior()) { + var blockA = this.sourceBlock_; + var blockB = target.getSourceBlock(); + } else { + var blockB = this.sourceBlock_; + var blockA = target.getSourceBlock(); + } + if (blockA && blockA == blockB) { + return Blockly.Connection.REASON_SELF_CONNECTION; + } else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) { + return Blockly.Connection.REASON_WRONG_TYPE; + } else if (blockA && blockB && blockA.workspace !== blockB.workspace) { + return Blockly.Connection.REASON_DIFFERENT_WORKSPACES; + } else if (!this.checkType_(target)) { + return Blockly.Connection.REASON_CHECKS_FAILED; + } else if (blockA.isShadow() && !blockB.isShadow()) { + return Blockly.Connection.REASON_SHADOW_PARENT; + } + return Blockly.Connection.CAN_CONNECT; +}; + +/** + * Checks whether the current connection and target connection are compatible + * and throws an exception if they are not. + * @param {Blockly.Connection} target The connection to check compatibility + * with. + * @private + */ +Blockly.Connection.prototype.checkConnection_ = function(target) { + switch (this.canConnectWithReason_(target)) { + case Blockly.Connection.CAN_CONNECT: + break; + case Blockly.Connection.REASON_SELF_CONNECTION: + throw 'Attempted to connect a block to itself.'; + case Blockly.Connection.REASON_DIFFERENT_WORKSPACES: + // Usually this means one block has been deleted. + throw 'Blocks not on same workspace.'; + case Blockly.Connection.REASON_WRONG_TYPE: + throw 'Attempt to connect incompatible types.'; + case Blockly.Connection.REASON_TARGET_NULL: + throw 'Target connection is null.'; + case Blockly.Connection.REASON_CHECKS_FAILED: + throw 'Connection checks failed.'; + case Blockly.Connection.REASON_SHADOW_PARENT: + throw 'Connecting non-shadow to shadow block.'; + default: + throw 'Unknown connection failure: this should never happen!'; + } +}; + +/** + * Check if the two connections can be dragged to connect to each other. + * @param {!Blockly.Connection} candidate A nearby connection to check. + * @return {boolean} True if the connection is allowed, false otherwise. + */ +Blockly.Connection.prototype.isConnectionAllowed = function(candidate) { + // Type checking. + var canConnect = this.canConnectWithReason_(candidate); + if (canConnect != Blockly.Connection.CAN_CONNECT) { + return false; + } + + // Don't offer to connect an already connected left (male) value plug to + // an available right (female) value plug. Don't offer to connect the + // bottom of a statement block to one that's already connected. + if (candidate.type == Blockly.OUTPUT_VALUE || + candidate.type == Blockly.PREVIOUS_STATEMENT) { + if (candidate.isConnected() || this.isConnected()) { + return false; + } + } + + // Offering to connect the left (male) of a value block to an already + // connected value pair is ok, we'll splice it in. + // However, don't offer to splice into an immovable block. + if (candidate.type == Blockly.INPUT_VALUE && candidate.isConnected() && + !candidate.targetBlock().isMovable() && + !candidate.targetBlock().isShadow()) { + return false; + } + + // Don't let a block with no next connection bump other blocks out of the + // stack. But covering up a shadow block or stack of shadow blocks is fine. + // Similarly, replacing a terminal statement with another terminal statement + // is allowed. + if (this.type == Blockly.PREVIOUS_STATEMENT && + candidate.isConnected() && + !this.sourceBlock_.nextConnection && + !candidate.targetBlock().isShadow() && + candidate.targetBlock().nextConnection) { + return false; + } + + // Don't let blocks try to connect to themselves or ones they nest. + if (Blockly.draggingConnections_.indexOf(candidate) != -1) { + return false; + } + + return true; +}; + +/** + * Connect this connection to another connection. + * @param {!Blockly.Connection} otherConnection Connection to connect to. + */ +Blockly.Connection.prototype.connect = function(otherConnection) { + if (this.targetConnection == otherConnection) { + // Already connected together. NOP. + return; + } + this.checkConnection_(otherConnection); + // Determine which block is superior (higher in the source stack). + if (this.isSuperior()) { + // Superior block. + this.connect_(otherConnection); + } else { + // Inferior block. + otherConnection.connect_(this); + } +}; + +/** + * Update two connections to target each other. + * @param {Blockly.Connection} first The first connection to update. + * @param {Blockly.Connection} second The second conneciton to update. + * @private + */ +Blockly.Connection.connectReciprocally_ = function(first, second) { + goog.asserts.assert(first && second, 'Cannot connect null connections.'); + first.targetConnection = second; + second.targetConnection = first; +}; + +/** + * Does the given block have one and only one connection point that will accept + * an orphaned block? + * @param {!Blockly.Block} block The superior block. + * @param {!Blockly.Block} orphanBlock The inferior block. + * @return {Blockly.Connection} The suitable connection point on 'block', + * or null. + * @private + */ +Blockly.Connection.singleConnection_ = function(block, orphanBlock) { + var connection = false; + for (var i = 0; i < block.inputList.length; i++) { + var thisConnection = block.inputList[i].connection; + if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE && + orphanBlock.outputConnection.checkType_(thisConnection)) { + if (connection) { + return null; // More than one connection. + } + connection = thisConnection; + } + } + return connection; +}; + +/** + * Walks down a row a blocks, at each stage checking if there are any + * connections that will accept the orphaned block. If at any point there + * are zero or multiple eligible connections, returns null. Otherwise + * returns the only input on the last block in the chain. + * Terminates early for shadow blocks. + * @param {!Blockly.Block} startBlock The block on which to start the search. + * @param {!Blockly.Block} orphanBlock The block that is looking for a home. + * @return {Blockly.Connection} The suitable connection point on the chain + * of blocks, or null. + * @private + */ +Blockly.Connection.lastConnectionInRow_ = function(startBlock, orphanBlock) { + var newBlock = startBlock; + var connection; + while (connection = Blockly.Connection.singleConnection_( + /** @type {!Blockly.Block} */ (newBlock), orphanBlock)) { + // '=' is intentional in line above. + newBlock = connection.targetBlock(); + if (!newBlock || newBlock.isShadow()) { + return connection; + } + } + return null; +}; + +/** + * Disconnect this connection. + */ +Blockly.Connection.prototype.disconnect = function() { + var otherConnection = this.targetConnection; + goog.asserts.assert(otherConnection, 'Source connection not connected.'); + goog.asserts.assert(otherConnection.targetConnection == this, + 'Target connection not connected to source connection.'); + + var parentBlock, childBlock, parentConnection; + if (this.isSuperior()) { + // Superior block. + parentBlock = this.sourceBlock_; + childBlock = otherConnection.getSourceBlock(); + parentConnection = this; + } else { + // Inferior block. + parentBlock = otherConnection.getSourceBlock(); + childBlock = this.sourceBlock_; + parentConnection = otherConnection; + } + this.disconnectInternal_(parentBlock, childBlock); + parentConnection.respawnShadow_(); +}; + +/** + * Disconnect two blocks that are connected by this connection. + * @param {!Blockly.Block} parentBlock The superior block. + * @param {!Blockly.Block} childBlock The inferior block. + * @private + */ +Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock, + childBlock) { + var event; + if (Blockly.Events.isEnabled()) { + event = new Blockly.Events.Move(childBlock); + } + var otherConnection = this.targetConnection; + otherConnection.targetConnection = null; + this.targetConnection = null; + childBlock.setParent(null); + if (event) { + event.recordNew(); + Blockly.Events.fire(event); + } +}; + +/** + * Respawn the shadow block if there was one connected to the this connection. + * @private + */ +Blockly.Connection.prototype.respawnShadow_ = function() { + var parentBlock = this.getSourceBlock(); + var shadow = this.getShadowDom(); + if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) { + var blockShadow = + Blockly.Xml.domToBlock(shadow, parentBlock.workspace); + if (blockShadow.outputConnection) { + this.connect(blockShadow.outputConnection); + } else if (blockShadow.previousConnection) { + this.connect(blockShadow.previousConnection); + } else { + throw 'Child block does not have output or previous statement.'; + } + } +}; + +/** + * Returns the block that this connection connects to. + * @return {Blockly.Block} The connected block or null if none is connected. + */ +Blockly.Connection.prototype.targetBlock = function() { + if (this.isConnected()) { + return this.targetConnection.getSourceBlock(); + } + return null; +}; + +/** + * Is this connection compatible with another connection with respect to the + * value type system. E.g. square_root("Hello") is not compatible. + * @param {!Blockly.Connection} otherConnection Connection to compare against. + * @return {boolean} True if the connections share a type. + * @private + */ +Blockly.Connection.prototype.checkType_ = function(otherConnection) { + if (!this.check_ || !otherConnection.check_) { + // One or both sides are promiscuous enough that anything will fit. + return true; + } + // Find any intersection in the check lists. + for (var i = 0; i < this.check_.length; i++) { + if (otherConnection.check_.indexOf(this.check_[i]) != -1) { + return true; + } + } + // No intersection. + return false; +}; + +/** + * Change a connection's compatibility. + * @param {*} check Compatible value type or list of value types. + * Null if all types are compatible. + * @return {!Blockly.Connection} The connection being modified + * (to allow chaining). + */ +Blockly.Connection.prototype.setCheck = function(check) { + if (check) { + // Ensure that check is in an array. + if (!goog.isArray(check)) { + check = [check]; + } + this.check_ = check; + // The new value type may not be compatible with the existing connection. + if (this.isConnected() && !this.checkType_(this.targetConnection)) { + var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; + child.unplug(); + // Bump away. + this.sourceBlock_.bumpNeighbours_(); + } + } else { + this.check_ = null; + } + return this; +}; + +/** + * Change a connection's shadow block. + * @param {Element} shadow DOM representation of a block or null. + */ +Blockly.Connection.prototype.setShadowDom = function(shadow) { + this.shadowDom_ = shadow; +}; + +/** + * Return a connection's shadow block. + * @return {Element} shadow DOM representation of a block or null. + */ +Blockly.Connection.prototype.getShadowDom = function() { + return this.shadowDom_; +}; diff --git a/src/blockly/core/connection_db.js b/src/blockly/core/connection_db.js new file mode 100644 index 0000000..8b3c300 --- /dev/null +++ b/src/blockly/core/connection_db.js @@ -0,0 +1,301 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Components for managing connections between blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.ConnectionDB'); + +goog.require('Blockly.Connection'); + + +/** + * Database of connections. + * Connections are stored in order of their vertical component. This way + * connections in an area may be looked up quickly using a binary search. + * @constructor + */ +Blockly.ConnectionDB = function() { +}; + +Blockly.ConnectionDB.prototype = new Array(); +/** + * Don't inherit the constructor from Array. + * @type {!Function} + */ +Blockly.ConnectionDB.constructor = Blockly.ConnectionDB; + +/** + * Add a connection to the database. Must not already exist in DB. + * @param {!Blockly.Connection} connection The connection to be added. + */ +Blockly.ConnectionDB.prototype.addConnection = function(connection) { + if (connection.inDB_) { + throw 'Connection already in database.'; + } + if (connection.getSourceBlock().isInFlyout) { + // Don't bother maintaining a database of connections in a flyout. + return; + } + var position = this.findPositionForConnection_(connection); + this.splice(position, 0, connection); + connection.inDB_ = true; +}; + +/** + * Find the given connection. + * Starts by doing a binary search to find the approximate location, then + * linearly searches nearby for the exact connection. + * @param {!Blockly.Connection} conn The connection to find. + * @return {number} The index of the connection, or -1 if the connection was + * not found. + */ +Blockly.ConnectionDB.prototype.findConnection = function(conn) { + if (!this.length) { + return -1; + } + + var bestGuess = this.findPositionForConnection_(conn); + if (bestGuess >= this.length) { + // Not in list + return -1; + } + + var yPos = conn.y_; + // Walk forward and back on the y axis looking for the connection. + var pointerMin = bestGuess; + var pointerMax = bestGuess; + while (pointerMin >= 0 && this[pointerMin].y_ == yPos) { + if (this[pointerMin] == conn) { + return pointerMin; + } + pointerMin--; + } + + while (pointerMax < this.length && this[pointerMax].y_ == yPos) { + if (this[pointerMax] == conn) { + return pointerMax; + } + pointerMax++; + } + return -1; +}; + +/** + * Finds a candidate position for inserting this connection into the list. + * This will be in the correct y order but makes no guarantees about ordering in + * the x axis. + * @param {!Blockly.Connection} connection The connection to insert. + * @return {number} The candidate index. + * @private + */ +Blockly.ConnectionDB.prototype.findPositionForConnection_ = + function(connection) { + /* eslint-disable indent */ + if (!this.length) { + return 0; + } + var pointerMin = 0; + var pointerMax = this.length; + while (pointerMin < pointerMax) { + var pointerMid = Math.floor((pointerMin + pointerMax) / 2); + if (this[pointerMid].y_ < connection.y_) { + pointerMin = pointerMid + 1; + } else if (this[pointerMid].y_ > connection.y_) { + pointerMax = pointerMid; + } else { + pointerMin = pointerMid; + break; + } + } + return pointerMin; +}; /* eslint-enable indent */ + +/** + * Remove a connection from the database. Must already exist in DB. + * @param {!Blockly.Connection} connection The connection to be removed. + * @private + */ +Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) { + if (!connection.inDB_) { + throw 'Connection not in database.'; + } + var removalIndex = this.findConnection(connection); + if (removalIndex == -1) { + throw 'Unable to find connection in connectionDB.'; + } + connection.inDB_ = false; + this.splice(removalIndex, 1); +}; + +/** + * Find all nearby connections to the given connection. + * Type checking does not apply, since this function is used for bumping. + * @param {!Blockly.Connection} connection The connection whose neighbours + * should be returned. + * @param {number} maxRadius The maximum radius to another connection. + * @return {!Array.} List of connections. + */ +Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) { + var db = this; + var currentX = connection.x_; + var currentY = connection.y_; + + // Binary search to find the closest y location. + var pointerMin = 0; + var pointerMax = db.length - 2; + var pointerMid = pointerMax; + while (pointerMin < pointerMid) { + if (db[pointerMid].y_ < currentY) { + pointerMin = pointerMid; + } else { + pointerMax = pointerMid; + } + pointerMid = Math.floor((pointerMin + pointerMax) / 2); + } + + var neighbours = []; + /** + * Computes if the current connection is within the allowed radius of another + * connection. + * This function is a closure and has access to outside variables. + * @param {number} yIndex The other connection's index in the database. + * @return {boolean} True if the current connection's vertical distance from + * the other connection is less than the allowed radius. + */ + function checkConnection_(yIndex) { + var dx = currentX - db[yIndex].x_; + var dy = currentY - db[yIndex].y_; + var r = Math.sqrt(dx * dx + dy * dy); + if (r <= maxRadius) { + neighbours.push(db[yIndex]); + } + return dy < maxRadius; + } + + // Walk forward and back on the y axis looking for the closest x,y point. + pointerMin = pointerMid; + pointerMax = pointerMid; + if (db.length) { + while (pointerMin >= 0 && checkConnection_(pointerMin)) { + pointerMin--; + } + do { + pointerMax++; + } while (pointerMax < db.length && checkConnection_(pointerMax)); + } + + return neighbours; +}; + + +/** + * Is the candidate connection close to the reference connection. + * Extremely fast; only looks at Y distance. + * @param {number} index Index in database of candidate connection. + * @param {number} baseY Reference connection's Y value. + * @param {number} maxRadius The maximum radius to another connection. + * @return {boolean} True if connection is in range. + * @private + */ +Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) { + return (Math.abs(this[index].y_ - baseY) <= maxRadius); +}; + +/** + * Find the closest compatible connection to this connection. + * @param {!Blockly.Connection} conn The connection searching for a compatible + * mate. + * @param {number} maxRadius The maximum radius to another connection. + * @param {!goog.math.Coordinate} dxy Offset between this connection's location + * in the database and the current location (as a result of dragging). + * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two + * properties:' connection' which is either another connection or null, + * and 'radius' which is the distance. + */ +Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, + dxy) { + // Don't bother. + if (!this.length) { + return {connection: null, radius: maxRadius}; + } + + // Stash the values of x and y from before the drag. + var baseY = conn.y_; + var baseX = conn.x_; + + conn.x_ = baseX + dxy.x; + conn.y_ = baseY + dxy.y; + + // findPositionForConnection finds an index for insertion, which is always + // after any block with the same y index. We want to search both forward + // and back, so search on both sides of the index. + var closestIndex = this.findPositionForConnection_(conn); + + var bestConnection = null; + var bestRadius = maxRadius; + var temp; + + // Walk forward and back on the y axis looking for the closest x,y point. + var pointerMin = closestIndex - 1; + while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y_, maxRadius)) { + temp = this[pointerMin]; + if (conn.isConnectionAllowed(temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } + pointerMin--; + } + + var pointerMax = closestIndex; + while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_, + maxRadius)) { + temp = this[pointerMax]; + if (conn.isConnectionAllowed(temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } + pointerMax++; + } + + // Reset the values of x and y. + conn.x_ = baseX; + conn.y_ = baseY; + + // If there were no valid connections, bestConnection will be null. + return {connection: bestConnection, radius: bestRadius}; +}; + +/** + * Initialize a set of connection DBs for a specified workspace. + * @param {!Blockly.Workspace} workspace The workspace this DB is for. + */ +Blockly.ConnectionDB.init = function(workspace) { + // Create four databases, one for each connection type. + var dbList = []; + dbList[Blockly.INPUT_VALUE] = new Blockly.ConnectionDB(); + dbList[Blockly.OUTPUT_VALUE] = new Blockly.ConnectionDB(); + dbList[Blockly.NEXT_STATEMENT] = new Blockly.ConnectionDB(); + dbList[Blockly.PREVIOUS_STATEMENT] = new Blockly.ConnectionDB(); + workspace.connectionDBList = dbList; +}; diff --git a/src/blockly/core/constants.js b/src/blockly/core/constants.js new file mode 100644 index 0000000..0f327e2 --- /dev/null +++ b/src/blockly/core/constants.js @@ -0,0 +1,202 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blockly constants. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.constants'); + + +/** + * Number of pixels the mouse must move before a drag starts. + */ +Blockly.DRAG_RADIUS = 5; + +/** + * Maximum misalignment between connections for them to snap together. + */ +Blockly.SNAP_RADIUS = 20; + +/** + * Delay in ms between trigger and bumping unconnected block out of alignment. + */ +Blockly.BUMP_DELAY = 250; + +/** + * Number of characters to truncate a collapsed block to. + */ +Blockly.COLLAPSE_CHARS = 30; + +/** + * Length in ms for a touch to become a long press. + */ +Blockly.LONGPRESS = 750; + +/** + * Prevent a sound from playing if another sound preceded it within this many + * miliseconds. + */ +Blockly.SOUND_LIMIT = 100; + +/** + * The richness of block colours, regardless of the hue. + * Must be in the range of 0 (inclusive) to 1 (exclusive). + */ +Blockly.HSV_SATURATION = 0.45; + +/** + * The intensity of block colours, regardless of the hue. + * Must be in the range of 0 (inclusive) to 1 (exclusive). + */ +Blockly.HSV_VALUE = 0.65; + +/** + * Sprited icons and images. + */ +Blockly.SPRITE = { + width: 96, + height: 124, + url: 'sprites.png' +}; + +// Constants below this point are not intended to be changed. + +/** + * Required name space for SVG elements. + * @const + */ +Blockly.SVG_NS = 'http://www.w3.org/2000/svg'; + +/** + * Required name space for HTML elements. + * @const + */ +Blockly.HTML_NS = 'http://www.w3.org/1999/xhtml'; + +/** + * ENUM for a right-facing value input. E.g. 'set item to' or 'return'. + * @const + */ +Blockly.INPUT_VALUE = 1; + +/** + * ENUM for a left-facing value output. E.g. 'random fraction'. + * @const + */ +Blockly.OUTPUT_VALUE = 2; + +/** + * ENUM for a down-facing block stack. E.g. 'if-do' or 'else'. + * @const + */ +Blockly.NEXT_STATEMENT = 3; + +/** + * ENUM for an up-facing block stack. E.g. 'break out of loop'. + * @const + */ +Blockly.PREVIOUS_STATEMENT = 4; + +/** + * ENUM for an dummy input. Used to add field(s) with no input. + * @const + */ +Blockly.DUMMY_INPUT = 5; + +/** + * ENUM for left alignment. + * @const + */ +Blockly.ALIGN_LEFT = -1; + +/** + * ENUM for centre alignment. + * @const + */ +Blockly.ALIGN_CENTRE = 0; + +/** + * ENUM for right alignment. + * @const + */ +Blockly.ALIGN_RIGHT = 1; + +/** + * ENUM for no drag operation. + * @const + */ +Blockly.DRAG_NONE = 0; + +/** + * ENUM for inside the sticky DRAG_RADIUS. + * @const + */ +Blockly.DRAG_STICKY = 1; + +/** + * ENUM for inside the non-sticky DRAG_RADIUS, for differentiating between + * clicks and drags. + * @const + */ +Blockly.DRAG_BEGIN = 1; + +/** + * ENUM for freely draggable (outside the DRAG_RADIUS, if one applies). + * @const + */ +Blockly.DRAG_FREE = 2; + +/** + * Lookup table for determining the opposite type of a connection. + * @const + */ +Blockly.OPPOSITE_TYPE = []; +Blockly.OPPOSITE_TYPE[Blockly.INPUT_VALUE] = Blockly.OUTPUT_VALUE; +Blockly.OPPOSITE_TYPE[Blockly.OUTPUT_VALUE] = Blockly.INPUT_VALUE; +Blockly.OPPOSITE_TYPE[Blockly.NEXT_STATEMENT] = Blockly.PREVIOUS_STATEMENT; +Blockly.OPPOSITE_TYPE[Blockly.PREVIOUS_STATEMENT] = Blockly.NEXT_STATEMENT; + + +/** + * ENUM for toolbox and flyout at top of screen. + * @const + */ +Blockly.TOOLBOX_AT_TOP = 0; + +/** + * ENUM for toolbox and flyout at bottom of screen. + * @const + */ +Blockly.TOOLBOX_AT_BOTTOM = 1; + +/** + * ENUM for toolbox and flyout at left of screen. + * @const + */ +Blockly.TOOLBOX_AT_LEFT = 2; + +/** + * ENUM for toolbox and flyout at right of screen. + * @const + */ +Blockly.TOOLBOX_AT_RIGHT = 3; diff --git a/src/blockly/core/contextmenu.js b/src/blockly/core/contextmenu.js new file mode 100644 index 0000000..462ad0b --- /dev/null +++ b/src/blockly/core/contextmenu.js @@ -0,0 +1,148 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Functionality for the right-click context menus. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.ContextMenu'); + +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.style'); +goog.require('goog.ui.Menu'); +goog.require('goog.ui.MenuItem'); + + +/** + * Which block is the context menu attached to? + * @type {Blockly.Block} + */ +Blockly.ContextMenu.currentBlock = null; + +/** + * Construct the menu based on the list of options and show the menu. + * @param {!Event} e Mouse event. + * @param {!Array.} options Array of menu options. + * @param {boolean} rtl True if RTL, false if LTR. + */ +Blockly.ContextMenu.show = function(e, options, rtl) { + Blockly.WidgetDiv.show(Blockly.ContextMenu, rtl, null); + if (!options.length) { + Blockly.ContextMenu.hide(); + return; + } + /* Here's what one option object looks like: + {text: 'Make It So', + enabled: true, + callback: Blockly.MakeItSo} + */ + var menu = new goog.ui.Menu(); + menu.setRightToLeft(rtl); + for (var i = 0, option; option = options[i]; i++) { + var menuItem = new goog.ui.MenuItem(option.text); + menuItem.setRightToLeft(rtl); + menu.addChild(menuItem, true); + menuItem.setEnabled(option.enabled); + if (option.enabled) { + goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION, + option.callback); + } + } + goog.events.listen(menu, goog.ui.Component.EventType.ACTION, + Blockly.ContextMenu.hide); + // Record windowSize and scrollOffset before adding menu. + var windowSize = goog.dom.getViewportSize(); + var scrollOffset = goog.style.getViewportPageOffset(document); + var div = Blockly.WidgetDiv.DIV; + menu.render(div); + var menuDom = menu.getElement(); + Blockly.addClass_(menuDom, 'blocklyContextMenu'); + // Prevent system context menu when right-clicking a Blockly context menu. + Blockly.bindEvent_(menuDom, 'contextmenu', null, Blockly.noEvent); + // Record menuSize after adding menu. + var menuSize = goog.style.getSize(menuDom); + + // Position the menu. + var x = e.clientX + scrollOffset.x; + var y = e.clientY + scrollOffset.y; + // Flip menu vertically if off the bottom. + if (e.clientY + menuSize.height >= windowSize.height) { + y -= menuSize.height; + } + // Flip menu horizontally if off the edge. + if (rtl) { + if (menuSize.width >= e.clientX) { + x += menuSize.width; + } + } else { + if (e.clientX + menuSize.width >= windowSize.width) { + x -= menuSize.width; + } + } + Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl); + + menu.setAllowAutoFocus(true); + // 1ms delay is required for focusing on context menus because some other + // mouse event is still waiting in the queue and clears focus. + setTimeout(function() {menuDom.focus();}, 1); + Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block. +}; + +/** + * Hide the context menu. + */ +Blockly.ContextMenu.hide = function() { + Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu); + Blockly.ContextMenu.currentBlock = null; +}; + +/** + * Create a callback function that creates and configures a block, + * then places the new block next to the original. + * @param {!Blockly.Block} block Original block. + * @param {!Element} xml XML representation of new block. + * @return {!Function} Function that creates a block. + */ +Blockly.ContextMenu.callbackFactory = function(block, xml) { + return function() { + Blockly.Events.disable(); + try { + var newBlock = Blockly.Xml.domToBlock(xml, block.workspace); + // Move the new block next to the old block. + var xy = block.getRelativeToSurfaceXY(); + if (block.RTL) { + xy.x -= Blockly.SNAP_RADIUS; + } else { + xy.x += Blockly.SNAP_RADIUS; + } + xy.y += Blockly.SNAP_RADIUS * 2; + newBlock.moveBy(xy.x, xy.y); + } finally { + Blockly.Events.enable(); + } + if (Blockly.Events.isEnabled() && !newBlock.isShadow()) { + Blockly.Events.fire(new Blockly.Events.Create(newBlock)); + } + newBlock.select(); + }; +}; diff --git a/src/blockly/core/css.js b/src/blockly/core/css.js new file mode 100644 index 0000000..c45ccc9 --- /dev/null +++ b/src/blockly/core/css.js @@ -0,0 +1,782 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Inject Blockly's CSS synchronously. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Css'); + + +/** + * List of cursors. + * @enum {string} + */ +Blockly.Css.Cursor = { + OPEN: 'handopen', + CLOSED: 'handclosed', + DELETE: 'handdelete' +}; + +/** + * Current cursor (cached value). + * @type {string} + * @private + */ +Blockly.Css.currentCursor_ = ''; + +/** + * Large stylesheet added by Blockly.Css.inject. + * @type {Element} + * @private + */ +Blockly.Css.styleSheet_ = null; + +/** + * Path to media directory, with any trailing slash removed. + * @type {string} + * @private + */ +Blockly.Css.mediaPath_ = ''; + +/** + * Inject the CSS into the DOM. This is preferable over using a regular CSS + * file since: + * a) It loads synchronously and doesn't force a redraw later. + * b) It speeds up loading by not blocking on a separate HTTP transfer. + * c) The CSS content may be made dynamic depending on init options. + * @param {boolean} hasCss If false, don't inject CSS + * (providing CSS becomes the document's responsibility). + * @param {string} pathToMedia Path from page to the Blockly media directory. + */ +Blockly.Css.inject = function(hasCss, pathToMedia) { + // Only inject the CSS once. + if (Blockly.Css.styleSheet_) { + return; + } + // Placeholder for cursor rule. Must be first rule (index 0). + var text = '.blocklyDraggable {}\n'; + if (hasCss) { + text += Blockly.Css.CONTENT.join('\n'); + if (Blockly.FieldDate) { + text += Blockly.FieldDate.CSS.join('\n'); + } + } + // Strip off any trailing slash (either Unix or Windows). + Blockly.Css.mediaPath_ = pathToMedia.replace(/[\\\/]$/, ''); + text = text.replace(/<<>>/g, Blockly.Css.mediaPath_); + // Inject CSS tag. + var cssNode = document.createElement('style'); + document.head.appendChild(cssNode); + var cssTextNode = document.createTextNode(text); + cssNode.appendChild(cssTextNode); + Blockly.Css.styleSheet_ = cssNode.sheet; + Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); +}; + +/** + * Set the cursor to be displayed when over something draggable. + * @param {Blockly.Css.Cursor} cursor Enum. + */ +Blockly.Css.setCursor = function(cursor) { + if (Blockly.Css.currentCursor_ == cursor) { + return; + } + Blockly.Css.currentCursor_ = cursor; + var url = 'url(' + Blockly.Css.mediaPath_ + '/' + cursor + '.cur), auto'; + // There are potentially hundreds of draggable objects. Changing their style + // properties individually is too slow, so change the CSS rule instead. + var rule = '.blocklyDraggable {\n cursor: ' + url + ';\n}\n'; + Blockly.Css.styleSheet_.deleteRule(0); + Blockly.Css.styleSheet_.insertRule(rule, 0); + // There is probably only one toolbox, so just change its style property. + var toolboxen = document.getElementsByClassName('blocklyToolboxDiv'); + for (var i = 0, toolbox; toolbox = toolboxen[i]; i++) { + if (cursor == Blockly.Css.Cursor.DELETE) { + toolbox.style.cursor = url; + } else { + toolbox.style.cursor = ''; + } + } + // Set cursor on the whole document, so that rapid movements + // don't result in cursor changing to an arrow momentarily. + var html = document.body.parentNode; + if (cursor == Blockly.Css.Cursor.OPEN) { + html.style.cursor = ''; + } else { + html.style.cursor = url; + } +}; + +/** + * Array making up the CSS content for Blockly. + */ +Blockly.Css.CONTENT = [ + '.blocklySvg {', + 'background-color: #fff;', + 'outline: none;', + 'overflow: hidden;', /* IE overflows by default. */ + 'display: block;', + '}', + + '.blocklyWidgetDiv {', + 'display: none;', + 'position: absolute;', + 'z-index: 99999;', /* big value for bootstrap3 compatibility */ + '}', + + '.injectionDiv {', + 'height: 100%;', + 'position: relative;', + '}', + + '.blocklyNonSelectable {', + 'user-select: none;', + '-moz-user-select: none;', + '-webkit-user-select: none;', + '-ms-user-select: none;', + '}', + + '.blocklyTooltipDiv {', + 'background-color: #ffffc7;', + 'border: 1px solid #ddc;', + 'box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);', + 'color: #000;', + 'display: none;', + 'font-family: sans-serif;', + 'font-size: 9pt;', + 'opacity: 0.9;', + 'padding: 2px;', + 'position: absolute;', + 'z-index: 100000;', /* big value for bootstrap3 compatibility */ + '}', + + '.blocklyResizeSE {', + 'cursor: se-resize;', + 'fill: #aaa;', + '}', + + '.blocklyResizeSW {', + 'cursor: sw-resize;', + 'fill: #aaa;', + '}', + + '.blocklyResizeLine {', + 'stroke: #888;', + 'stroke-width: 1;', + '}', + + '.blocklyHighlightedConnectionPath {', + 'fill: none;', + 'stroke: #fc3;', + 'stroke-width: 4px;', + '}', + + '.blocklyPathLight {', + 'fill: none;', + 'stroke-linecap: round;', + 'stroke-width: 1;', + '}', + + '.blocklySelected>.blocklyPath {', + 'stroke: #fc3;', + 'stroke-width: 3px;', + '}', + + '.blocklySelected>.blocklyPathLight {', + 'display: none;', + '}', + + '.blocklyDragging>.blocklyPath,', + '.blocklyDragging>.blocklyPathLight {', + 'fill-opacity: .8;', + 'stroke-opacity: .8;', + '}', + + '.blocklyDragging>.blocklyPathDark {', + 'display: none;', + '}', + + '.blocklyDisabled>.blocklyPath {', + 'fill-opacity: .5;', + 'stroke-opacity: .5;', + '}', + + '.blocklyDisabled>.blocklyPathLight,', + '.blocklyDisabled>.blocklyPathDark {', + 'display: none;', + '}', + + '.blocklyText {', + 'cursor: default;', + 'fill: #fff;', + 'font-family: sans-serif;', + 'font-size: 11pt;', + '}', + + '.blocklyNonEditableText>text {', + 'pointer-events: none;', + '}', + + '.blocklyNonEditableText>rect,', + '.blocklyEditableText>rect {', + 'fill: #fff;', + 'fill-opacity: .6;', + '}', + + '.blocklyNonEditableText>text,', + '.blocklyEditableText>text {', + 'fill: #000;', + '}', + + '.blocklyEditableText:hover>rect {', + 'stroke: #fff;', + 'stroke-width: 2;', + '}', + + '.blocklyBubbleText {', + 'fill: #000;', + '}', + + '.blocklyFlyoutButton {', + 'fill: #888;', + 'cursor: default', + '}', + + '.blocklyFlyoutButton:hover {', + 'fill: #ccc;', + '}', + + /* + Don't allow users to select text. It gets annoying when trying to + drag a block and selected text moves instead. + */ + '.blocklySvg text {', + 'user-select: none;', + '-moz-user-select: none;', + '-webkit-user-select: none;', + 'cursor: inherit;', + '}', + + '.blocklyHidden {', + 'display: none;', + '}', + + '.blocklyFieldDropdown:not(.blocklyHidden) {', + 'display: block;', + '}', + + '.blocklyIconGroup {', + 'cursor: default;', + '}', + + '.blocklyIconGroup:not(:hover),', + '.blocklyIconGroupReadonly {', + 'opacity: .6;', + '}', + + '.blocklyIconShape {', + 'fill: #00f;', + 'stroke: #fff;', + 'stroke-width: 1px;', + '}', + + '.blocklyIconSymbol {', + 'fill: #fff;', + '}', + + '.blocklyMinimalBody {', + 'margin: 0;', + 'padding: 0;', + '}', + + '.blocklyCommentTextarea {', + 'background-color: #ffc;', + 'border: 0;', + 'margin: 0;', + 'padding: 2px;', + 'resize: none;', + '}', + + '.blocklyHtmlInput {', + 'border: none;', + 'border-radius: 4px;', + 'font-family: sans-serif;', + 'height: 100%;', + 'margin: 0;', + 'outline: none;', + 'padding: 0 1px;', + 'width: 100%', + '}', + + '.blocklyMainBackground {', + 'stroke-width: 1;', + 'stroke: #c6c6c6;', /* Equates to #ddd due to border being off-pixel. */ + '}', + + '.blocklyMutatorBackground {', + 'fill: #fff;', + 'stroke: #ddd;', + 'stroke-width: 1;', + '}', + + '.blocklyFlyoutBackground {', + 'fill: #ddd;', + 'fill-opacity: .8;', + '}', + + '.blocklyScrollbarBackground {', + 'opacity: 0;', + '}', + + '.blocklyScrollbarHandle {', + 'fill: #ccc;', + '}', + + '.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,', + '.blocklyScrollbarHandle:hover {', + 'fill: #bbb;', + '}', + + '.blocklyZoom>image {', + 'opacity: .4;', + '}', + + '.blocklyZoom>image:hover {', + 'opacity: .6;', + '}', + + '.blocklyZoom>image:active {', + 'opacity: .8;', + '}', + + /* Darken flyout scrollbars due to being on a grey background. */ + /* By contrast, workspace scrollbars are on a white background. */ + '.blocklyFlyout .blocklyScrollbarHandle {', + 'fill: #bbb;', + '}', + + '.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,', + '.blocklyFlyout .blocklyScrollbarHandle:hover {', + 'fill: #aaa;', + '}', + + '.blocklyInvalidInput {', + 'background: #faa;', + '}', + + '.blocklyAngleCircle {', + 'stroke: #444;', + 'stroke-width: 1;', + 'fill: #ddd;', + 'fill-opacity: .8;', + '}', + + '.blocklyAngleMarks {', + 'stroke: #444;', + 'stroke-width: 1;', + '}', + + '.blocklyAngleGauge {', + 'fill: #f88;', + 'fill-opacity: .8;', + '}', + + '.blocklyAngleLine {', + 'stroke: #f00;', + 'stroke-width: 2;', + 'stroke-linecap: round;', + '}', + + '.blocklyContextMenu {', + 'border-radius: 4px;', + '}', + + '.blocklyDropdownMenu {', + 'padding: 0 !important;', + '}', + + /* Override the default Closure URL. */ + '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,', + '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {', + 'background: url(<<>>/sprites.png) no-repeat -48px -16px !important;', + '}', + + /* Category tree in Toolbox. */ + '.blocklyToolboxDiv {', + 'background-color: #ddd;', + 'overflow-x: visible;', + 'overflow-y: auto;', + 'position: absolute;', + '}', + + '.blocklyTreeRoot {', + 'padding: 4px 0;', + '}', + + '.blocklyTreeRoot:focus {', + 'outline: none;', + '}', + + '.blocklyTreeRow {', + 'height: 22px;', + 'line-height: 22px;', + 'margin-bottom: 3px;', + 'padding-right: 8px;', + 'white-space: nowrap;', + '}', + + '.blocklyHorizontalTree {', + 'float: left;', + 'margin: 1px 5px 8px 0;', + '}', + + '.blocklyHorizontalTreeRtl {', + 'float: right;', + 'margin: 1px 0 8px 5px;', + '}', + + '.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {', + 'margin-left: 8px;', + '}', + + '.blocklyTreeRow:not(.blocklyTreeSelected):hover {', + 'background-color: #e4e4e4;', + '}', + + '.blocklyTreeSeparator {', + 'border-bottom: solid #e5e5e5 1px;', + 'height: 0;', + 'margin: 5px 0;', + '}', + + '.blocklyTreeSeparatorHorizontal {', + 'border-right: solid #e5e5e5 1px;', + 'width: 0;', + 'padding: 5px 0;', + 'margin: 0 5px;', + '}', + + + '.blocklyTreeIcon {', + 'background-image: url(<<>>/sprites.png);', + 'height: 16px;', + 'vertical-align: middle;', + 'width: 16px;', + '}', + + '.blocklyTreeIconClosedLtr {', + 'background-position: -32px -1px;', + '}', + + '.blocklyTreeIconClosedRtl {', + 'background-position: 0px -1px;', + '}', + + '.blocklyTreeIconOpen {', + 'background-position: -16px -1px;', + '}', + + '.blocklyTreeSelected>.blocklyTreeIconClosedLtr {', + 'background-position: -32px -17px;', + '}', + + '.blocklyTreeSelected>.blocklyTreeIconClosedRtl {', + 'background-position: 0px -17px;', + '}', + + '.blocklyTreeSelected>.blocklyTreeIconOpen {', + 'background-position: -16px -17px;', + '}', + + '.blocklyTreeIconNone,', + '.blocklyTreeSelected>.blocklyTreeIconNone {', + 'background-position: -48px -1px;', + '}', + + '.blocklyTreeLabel {', + 'cursor: default;', + 'font-family: sans-serif;', + 'font-size: 16px;', + 'padding: 0 3px;', + 'vertical-align: middle;', + '}', + + '.blocklyTreeSelected .blocklyTreeLabel {', + 'color: #fff;', + '}', + + /* Copied from: goog/css/colorpicker-simplegrid.css */ + /* + * Copyright 2007 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /* Author: pupius@google.com (Daniel Pupius) */ + + /* + Styles to make the colorpicker look like the old gmail color picker + NOTE: without CSS scoping this will override styles defined in palette.css + */ + '.blocklyWidgetDiv .goog-palette {', + 'outline: none;', + 'cursor: default;', + '}', + + '.blocklyWidgetDiv .goog-palette-table {', + 'border: 1px solid #666;', + 'border-collapse: collapse;', + '}', + + '.blocklyWidgetDiv .goog-palette-cell {', + 'height: 13px;', + 'width: 15px;', + 'margin: 0;', + 'border: 0;', + 'text-align: center;', + 'vertical-align: middle;', + 'border-right: 1px solid #666;', + 'font-size: 1px;', + '}', + + '.blocklyWidgetDiv .goog-palette-colorswatch {', + 'position: relative;', + 'height: 13px;', + 'width: 15px;', + 'border: 1px solid #666;', + '}', + + '.blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {', + 'border: 1px solid #FFF;', + '}', + + '.blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {', + 'border: 1px solid #000;', + 'color: #fff;', + '}', + + /* Copied from: goog/css/menu.css */ + /* + * Copyright 2009 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /** + * Standard styling for menus created by goog.ui.MenuRenderer. + * + * @author attila@google.com (Attila Bodis) + */ + + '.blocklyWidgetDiv .goog-menu {', + 'background: #fff;', + 'border-color: #ccc #666 #666 #ccc;', + 'border-style: solid;', + 'border-width: 1px;', + 'cursor: default;', + 'font: normal 13px Arial, sans-serif;', + 'margin: 0;', + 'outline: none;', + 'padding: 4px 0;', + 'position: absolute;', + 'overflow-y: auto;', + 'overflow-x: hidden;', + 'max-height: 100%;', + 'z-index: 20000;', /* Arbitrary, but some apps depend on it... */ + '}', + + /* Copied from: goog/css/menuitem.css */ + /* + * Copyright 2009 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /** + * Standard styling for menus created by goog.ui.MenuItemRenderer. + * + * @author attila@google.com (Attila Bodis) + */ + + /** + * State: resting. + * + * NOTE(mleibman,chrishenry): + * The RTL support in Closure is provided via two mechanisms -- "rtl" CSS + * classes and BiDi flipping done by the CSS compiler. Closure supports RTL + * with or without the use of the CSS compiler. In order for them not + * to conflict with each other, the "rtl" CSS classes need to have the #noflip + * annotation. The non-rtl counterparts should ideally have them as well, but, + * since .goog-menuitem existed without .goog-menuitem-rtl for so long before + * being added, there is a risk of people having templates where they are not + * rendering the .goog-menuitem-rtl class when in RTL and instead rely solely + * on the BiDi flipping by the CSS compiler. That's why we're not adding the + * #noflip to .goog-menuitem. + */ + '.blocklyWidgetDiv .goog-menuitem {', + 'color: #000;', + 'font: normal 13px Arial, sans-serif;', + 'list-style: none;', + 'margin: 0;', + /* 28px on the left for icon or checkbox; 7em on the right for shortcut. */ + 'padding: 4px 7em 4px 28px;', + 'white-space: nowrap;', + '}', + + /* BiDi override for the resting state. */ + /* #noflip */ + '.blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {', + /* Flip left/right padding for BiDi. */ + 'padding-left: 7em;', + 'padding-right: 28px;', + '}', + + /* If a menu doesn't have checkable items or items with icons, remove padding. */ + '.blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,', + '.blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {', + 'padding-left: 12px;', + '}', + + /* + * If a menu doesn't have items with shortcuts, leave just enough room for + * submenu arrows, if they are rendered. + */ + '.blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {', + 'padding-right: 20px;', + '}', + + '.blocklyWidgetDiv .goog-menuitem-content {', + 'color: #000;', + 'font: normal 13px Arial, sans-serif;', + '}', + + /* State: disabled. */ + '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,', + '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {', + 'color: #ccc !important;', + '}', + + '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {', + 'opacity: 0.3;', + '-moz-opacity: 0.3;', + 'filter: alpha(opacity=30);', + '}', + + /* State: hover. */ + '.blocklyWidgetDiv .goog-menuitem-highlight,', + '.blocklyWidgetDiv .goog-menuitem-hover {', + 'background-color: #d6e9f8;', + /* Use an explicit top and bottom border so that the selection is visible', + * in high contrast mode. */ + 'border-color: #d6e9f8;', + 'border-style: dotted;', + 'border-width: 1px 0;', + 'padding-bottom: 3px;', + 'padding-top: 3px;', + '}', + + /* State: selected/checked. */ + '.blocklyWidgetDiv .goog-menuitem-checkbox,', + '.blocklyWidgetDiv .goog-menuitem-icon {', + 'background-repeat: no-repeat;', + 'height: 16px;', + 'left: 6px;', + 'position: absolute;', + 'right: auto;', + 'vertical-align: middle;', + 'width: 16px;', + '}', + + /* BiDi override for the selected/checked state. */ + /* #noflip */ + '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,', + '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {', + /* Flip left/right positioning. */ + 'left: auto;', + 'right: 6px;', + '}', + + '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,', + '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {', + /* Client apps may override the URL at which they serve the sprite. */ + 'background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;', + '}', + + /* Keyboard shortcut ("accelerator") style. */ + '.blocklyWidgetDiv .goog-menuitem-accel {', + 'color: #999;', + /* Keyboard shortcuts are untranslated; always left-to-right. */ + /* #noflip */ + 'direction: ltr;', + 'left: auto;', + 'padding: 0 6px;', + 'position: absolute;', + 'right: 0;', + 'text-align: right;', + '}', + + /* BiDi override for shortcut style. */ + /* #noflip */ + '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {', + /* Flip left/right positioning and text alignment. */ + 'left: 0;', + 'right: auto;', + 'text-align: left;', + '}', + + /* Mnemonic styles. */ + '.blocklyWidgetDiv .goog-menuitem-mnemonic-hint {', + 'text-decoration: underline;', + '}', + + '.blocklyWidgetDiv .goog-menuitem-mnemonic-separator {', + 'color: #999;', + 'font-size: 12px;', + 'padding-left: 4px;', + '}', + + /* Copied from: goog/css/menuseparator.css */ + /* + * Copyright 2009 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /** + * Standard styling for menus created by goog.ui.MenuSeparatorRenderer. + * + * @author attila@google.com (Attila Bodis) + */ + + '.blocklyWidgetDiv .goog-menuseparator {', + 'border-top: 1px solid #ccc;', + 'margin: 4px 0;', + 'padding: 0;', + '}', + + '' +]; diff --git a/src/blockly/core/events.js b/src/blockly/core/events.js new file mode 100644 index 0000000..1d1e2b7 --- /dev/null +++ b/src/blockly/core/events.js @@ -0,0 +1,818 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Events fired as a result of actions in Blockly's editor. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Events'); + +goog.require('goog.math.Coordinate'); + + +/** + * Group ID for new events. Grouped events are indivisible. + * @type {string} + * @private + */ +Blockly.Events.group_ = ''; + +/** + * Sets whether events should be added to the undo stack. + * @type {boolean} + */ +Blockly.Events.recordUndo = true; + +/** + * Allow change events to be created and fired. + * @type {number} + * @private + */ +Blockly.Events.disabled_ = 0; + +/** + * Name of event that creates a block. + * @const + */ +Blockly.Events.CREATE = 'create'; + +/** + * Name of event that deletes a block. + * @const + */ +Blockly.Events.DELETE = 'delete'; + +/** + * Name of event that changes a block. + * @const + */ +Blockly.Events.CHANGE = 'change'; + +/** + * Name of event that moves a block. + * @const + */ +Blockly.Events.MOVE = 'move'; + +/** + * Name of event that records a UI change. + * @const + */ +Blockly.Events.UI = 'ui'; + +/** + * List of events queued for firing. + * @private + */ +Blockly.Events.FIRE_QUEUE_ = []; + +/** + * Create a custom event and fire it. + * @param {!Blockly.Events.Abstract} event Custom data for event. + */ +Blockly.Events.fire = function(event) { + if (!Blockly.Events.isEnabled()) { + return; + } + if (!Blockly.Events.FIRE_QUEUE_.length) { + // First event added; schedule a firing of the event queue. + setTimeout(Blockly.Events.fireNow_, 0); + } + Blockly.Events.FIRE_QUEUE_.push(event); +}; + +/** + * Fire all queued events. + * @private + */ +Blockly.Events.fireNow_ = function() { + var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_, true); + Blockly.Events.FIRE_QUEUE_.length = 0; + for (var i = 0, event; event = queue[i]; i++) { + var workspace = Blockly.Workspace.getById(event.workspaceId); + if (workspace) { + workspace.fireChangeListener(event); + } + } +}; + +/** + * Filter the queued events and merge duplicates. + * @param {!Array.} queueIn Array of events. + * @param {boolean} forward True if forward (redo), false if backward (undo). + * @return {!Array.} Array of filtered events. + */ +Blockly.Events.filter = function(queueIn, forward) { + var queue = goog.array.clone(queueIn); + if (!forward) { + // Undo is merged in reverse order. + queue.reverse(); + } + // Merge duplicates. O(n^2), but n should be very small. + for (var i = 0, event1; event1 = queue[i]; i++) { + for (var j = i + 1, event2; event2 = queue[j]; j++) { + if (event1.type == event2.type && + event1.blockId == event2.blockId && + event1.workspaceId == event2.workspaceId) { + if (event1.type == Blockly.Events.MOVE) { + // Merge move events. + event1.newParentId = event2.newParentId; + event1.newInputName = event2.newInputName; + event1.newCoordinate = event2.newCoordinate; + queue.splice(j, 1); + j--; + } else if (event1.type == Blockly.Events.CHANGE && + event1.element == event2.element && + event1.name == event2.name) { + // Merge change events. + event1.newValue = event2.newValue; + queue.splice(j, 1); + j--; + } else if (event1.type == Blockly.Events.UI && + event2.element == 'click' && + (event1.element == 'commentOpen' || + event1.element == 'mutatorOpen' || + event1.element == 'warningOpen')) { + // Merge change events. + event1.newValue = event2.newValue; + queue.splice(j, 1); + j--; + } + } + } + } + // Remove null events. + for (var i = queue.length - 1; i >= 0; i--) { + if (queue[i].isNull()) { + queue.splice(i, 1); + } + } + if (!forward) { + // Restore undo order. + queue.reverse(); + } + // Move mutation events to the top of the queue. + // Intentionally skip first event. + for (var i = 1, event; event = queue[i]; i++) { + if (event.type == Blockly.Events.CHANGE && + event.element == 'mutation') { + queue.unshift(queue.splice(i, 1)[0]); + } + } + return queue; +}; + +/** + * Modify pending undo events so that when they are fired they don't land + * in the undo stack. Called by Blockly.Workspace.clearUndo. + */ +Blockly.Events.clearPendingUndo = function() { + for (var i = 0, event; event = Blockly.Events.FIRE_QUEUE_[i]; i++) { + event.recordUndo = false; + } +}; + +/** + * Stop sending events. Every call to this function MUST also call enable. + */ +Blockly.Events.disable = function() { + Blockly.Events.disabled_++; +}; + +/** + * Start sending events. Unless events were already disabled when the + * corresponding call to disable was made. + */ +Blockly.Events.enable = function() { + Blockly.Events.disabled_--; +}; + +/** + * Returns whether events may be fired or not. + * @return {boolean} True if enabled. + */ +Blockly.Events.isEnabled = function() { + return Blockly.Events.disabled_ == 0; +}; + +/** + * Current group. + * @return {string} ID string. + */ +Blockly.Events.getGroup = function() { + return Blockly.Events.group_; +}; + +/** + * Start or stop a group. + * @param {boolean|string} state True to start new group, false to end group. + * String to set group explicitly. + */ +Blockly.Events.setGroup = function(state) { + if (typeof state == 'boolean') { + Blockly.Events.group_ = state ? Blockly.genUid() : ''; + } else { + Blockly.Events.group_ = state; + } +}; + +/** + * Compute a list of the IDs of the specified block and all its descendants. + * @param {!Blockly.Block} block The root block. + * @return {!Array.} List of block IDs. + * @private + */ +Blockly.Events.getDescendantIds_ = function(block) { + var ids = []; + var descendants = block.getDescendants(); + for (var i = 0, descendant; descendant = descendants[i]; i++) { + ids[i] = descendant.id; + } + return ids; +}; + +/** + * Decode the JSON into an event. + * @param {!Object} json JSON representation. + * @param {!Blockly.Workspace} workspace Target workspace for event. + * @return {!Blockly.Events.Abstract} The event represented by the JSON. + */ +Blockly.Events.fromJson = function(json, workspace) { + var event; + switch (json.type) { + case Blockly.Events.CREATE: + event = new Blockly.Events.Create(null); + break; + case Blockly.Events.DELETE: + event = new Blockly.Events.Delete(null); + break; + case Blockly.Events.CHANGE: + event = new Blockly.Events.Change(null); + break; + case Blockly.Events.MOVE: + event = new Blockly.Events.Move(null); + break; + case Blockly.Events.UI: + event = new Blockly.Events.Ui(null); + break; + default: + throw 'Unknown event type.'; + } + event.fromJson(json); + event.workspaceId = workspace.id; + return event; +}; + +/** + * Abstract class for an event. + * @param {Blockly.Block} block The block. + * @constructor + */ +Blockly.Events.Abstract = function(block) { + if (block) { + this.blockId = block.id; + this.workspaceId = block.workspace.id; + } + this.group = Blockly.Events.group_; + this.recordUndo = Blockly.Events.recordUndo; +}; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Abstract.prototype.toJson = function() { + var json = { + 'type': this.type, + }; + if (this.blockId) { + json['blockId'] = this.blockId; + } + if (this.group) { + json['group'] = this.group; + } + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Abstract.prototype.fromJson = function(json) { + this.blockId = json['blockId']; + this.group = json['group']; +}; + +/** + * Does this event record any change of state? + * @return {boolean} True if null, false if something changed. + */ +Blockly.Events.Abstract.prototype.isNull = function() { + return false; +}; + +/** + * Run an event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Abstract.prototype.run = function(forward) { + // Defined by subclasses. +}; + +/** + * Class for a block creation event. + * @param {Blockly.Block} block The created block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Create = function(block) { + if (!block) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.Create.superClass_.constructor.call(this, block); + this.xml = Blockly.Xml.blockToDomWithXY(block); + this.ids = Blockly.Events.getDescendantIds_(block); +}; +goog.inherits(Blockly.Events.Create, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Create.prototype.type = Blockly.Events.CREATE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Create.prototype.toJson = function() { + var json = Blockly.Events.Create.superClass_.toJson.call(this); + json['xml'] = Blockly.Xml.domToText(this.xml); + json['ids'] = this.ids; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Create.prototype.fromJson = function(json) { + Blockly.Events.Create.superClass_.fromJson.call(this, json); + this.xml = Blockly.Xml.textToDom('' + json['xml'] + '').firstChild; + this.ids = json['ids']; +}; + +/** + * Run a creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Create.prototype.run = function(forward) { + var workspace = Blockly.Workspace.getById(this.workspaceId); + if (forward) { + var xml = goog.dom.createDom('xml'); + xml.appendChild(this.xml); + Blockly.Xml.domToWorkspace(xml, workspace); + } else { + for (var i = 0, id; id = this.ids[i]; i++) { + var block = workspace.getBlockById(id); + if (block) { + block.dispose(false, false); + } else if (id == this.blockId) { + // Only complain about root-level block. + console.warn("Can't uncreate non-existant block: " + id); + } + } + } +}; + +/** + * Class for a block deletion event. + * @param {Blockly.Block} block The deleted block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Delete = function(block) { + if (!block) { + return; // Blank event to be populated by fromJson. + } + if (block.getParent()) { + throw 'Connected blocks cannot be deleted.'; + } + Blockly.Events.Delete.superClass_.constructor.call(this, block); + this.oldXml = Blockly.Xml.blockToDomWithXY(block); + this.ids = Blockly.Events.getDescendantIds_(block); +}; +goog.inherits(Blockly.Events.Delete, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Delete.prototype.type = Blockly.Events.DELETE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Delete.prototype.toJson = function() { + var json = Blockly.Events.Delete.superClass_.toJson.call(this); + json['ids'] = this.ids; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Delete.prototype.fromJson = function(json) { + Blockly.Events.Delete.superClass_.fromJson.call(this, json); + this.ids = json['ids']; +}; + +/** + * Run a deletion event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Delete.prototype.run = function(forward) { + var workspace = Blockly.Workspace.getById(this.workspaceId); + if (forward) { + for (var i = 0, id; id = this.ids[i]; i++) { + var block = workspace.getBlockById(id); + if (block) { + block.dispose(false, false); + } else if (id == this.blockId) { + // Only complain about root-level block. + console.warn("Can't delete non-existant block: " + id); + } + } + } else { + var xml = goog.dom.createDom('xml'); + xml.appendChild(this.oldXml); + Blockly.Xml.domToWorkspace(xml, workspace); + } +}; + +/** + * Class for a block change event. + * @param {Blockly.Block} block The changed block. Null for a blank event. + * @param {string} element One of 'field', 'comment', 'disabled', etc. + * @param {?string} name Name of input or field affected, or null. + * @param {string} oldValue Previous value of element. + * @param {string} newValue New value of element. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Change = function(block, element, name, oldValue, newValue) { + if (!block) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.Change.superClass_.constructor.call(this, block); + this.element = element; + this.name = name; + this.oldValue = oldValue; + this.newValue = newValue; +}; +goog.inherits(Blockly.Events.Change, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Change.prototype.type = Blockly.Events.CHANGE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Change.prototype.toJson = function() { + var json = Blockly.Events.Change.superClass_.toJson.call(this); + json['element'] = this.element; + if (this.name) { + json['name'] = this.name; + } + json['newValue'] = this.newValue; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Change.prototype.fromJson = function(json) { + Blockly.Events.Change.superClass_.fromJson.call(this, json); + this.element = json['element']; + this.name = json['name']; + this.newValue = json['newValue']; +}; + +/** + * Does this event record any change of state? + * @return {boolean} True if something changed. + */ +Blockly.Events.Change.prototype.isNull = function() { + return this.oldValue == this.newValue; +}; + +/** + * Run a change event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Change.prototype.run = function(forward) { + var workspace = Blockly.Workspace.getById(this.workspaceId); + var block = workspace.getBlockById(this.blockId); + if (!block) { + console.warn("Can't change non-existant block: " + this.blockId); + return; + } + if (block.mutator) { + // Close the mutator (if open) since we don't want to update it. + block.mutator.setVisible(false); + } + var value = forward ? this.newValue : this.oldValue; + switch (this.element) { + case 'field': + var field = block.getField(this.name); + if (field) { + // Run the validator for any side-effects it may have. + // The validator's opinion on validity is ignored. + field.callValidator(value); + field.setValue(value); + } else { + console.warn("Can't set non-existant field: " + this.name); + } + break; + case 'comment': + block.setCommentText(value || null); + break; + case 'collapsed': + block.setCollapsed(value); + break; + case 'disabled': + block.setDisabled(value); + break; + case 'inline': + block.setInputsInline(value); + break; + case 'mutation': + var oldMutation = ''; + if (block.mutationToDom) { + var oldMutationDom = block.mutationToDom(); + oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom); + } + if (block.domToMutation) { + value = value || ''; + var dom = Blockly.Xml.textToDom('' + value + ''); + block.domToMutation(dom.firstChild); + } + Blockly.Events.fire(new Blockly.Events.Change( + block, 'mutation', null, oldMutation, value)); + break; + default: + console.warn('Unknown change type: ' + this.element); + } +}; + +/** + * Class for a block move event. Created before the move. + * @param {Blockly.Block} block The moved block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Move = function(block) { + if (!block) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.Move.superClass_.constructor.call(this, block); + var location = this.currentLocation_(); + this.oldParentId = location.parentId; + this.oldInputName = location.inputName; + this.oldCoordinate = location.coordinate; +}; +goog.inherits(Blockly.Events.Move, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Move.prototype.type = Blockly.Events.MOVE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Move.prototype.toJson = function() { + var json = Blockly.Events.Move.superClass_.toJson.call(this); + if (this.newParentId) { + json['newParentId'] = this.newParentId; + } + if (this.newInputName) { + json['newInputName'] = this.newInputName; + } + if (this.newCoordinate) { + json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' + + Math.round(this.newCoordinate.y); + } + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Move.prototype.fromJson = function(json) { + Blockly.Events.Move.superClass_.fromJson.call(this, json); + this.newParentId = json['newParentId']; + this.newInputName = json['newInputName']; + if (json['newCoordinate']) { + var xy = json['newCoordinate'].split(','); + this.newCoordinate = + new goog.math.Coordinate(parseFloat(xy[0]), parseFloat(xy[1])); + } +}; + +/** + * Record the block's new location. Called after the move. + */ +Blockly.Events.Move.prototype.recordNew = function() { + var location = this.currentLocation_(); + this.newParentId = location.parentId; + this.newInputName = location.inputName; + this.newCoordinate = location.coordinate; +}; + +/** + * Returns the parentId and input if the block is connected, + * or the XY location if disconnected. + * @return {!Object} Collection of location info. + * @private + */ +Blockly.Events.Move.prototype.currentLocation_ = function() { + var workspace = Blockly.Workspace.getById(this.workspaceId); + var block = workspace.getBlockById(this.blockId); + var location = {}; + var parent = block.getParent(); + if (parent) { + location.parentId = parent.id; + var input = parent.getInputWithBlock(block); + if (input) { + location.inputName = input.name; + } + } else { + location.coordinate = block.getRelativeToSurfaceXY(); + } + return location; +}; + +/** + * Does this event record any change of state? + * @return {boolean} True if something changed. + */ +Blockly.Events.Move.prototype.isNull = function() { + return this.oldParentId == this.newParentId && + this.oldInputName == this.newInputName && + goog.math.Coordinate.equals(this.oldCoordinate, this.newCoordinate); +}; + +/** + * Run a move event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Move.prototype.run = function(forward) { + var workspace = Blockly.Workspace.getById(this.workspaceId); + var block = workspace.getBlockById(this.blockId); + if (!block) { + console.warn("Can't move non-existant block: " + this.blockId); + return; + } + var parentId = forward ? this.newParentId : this.oldParentId; + var inputName = forward ? this.newInputName : this.oldInputName; + var coordinate = forward ? this.newCoordinate : this.oldCoordinate; + var parentBlock = null; + if (parentId) { + parentBlock = workspace.getBlockById(parentId); + if (!parentBlock) { + console.warn("Can't connect to non-existant block: " + parentId); + return; + } + } + if (block.getParent()) { + block.unplug(); + } + if (coordinate) { + var xy = block.getRelativeToSurfaceXY(); + block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y); + } else { + var blockConnection = block.outputConnection || block.previousConnection; + var parentConnection; + if (inputName) { + var input = parentBlock.getInput(inputName); + if (input) { + parentConnection = input.connection; + } + } else if (blockConnection.type == Blockly.PREVIOUS_STATEMENT) { + parentConnection = parentBlock.nextConnection; + } + if (parentConnection) { + blockConnection.connect(parentConnection); + } else { + console.warn("Can't connect to non-existant input: " + inputName); + } + } +}; + +/** + * Class for a UI event. + * @param {Blockly.Block} block The affected block. + * @param {string} element One of 'selected', 'comment', 'mutator', etc. + * @param {string} oldValue Previous value of element. + * @param {string} newValue New value of element. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Ui = function(block, element, oldValue, newValue) { + Blockly.Events.Ui.superClass_.constructor.call(this, block); + this.element = element; + this.oldValue = oldValue; + this.newValue = newValue; + this.recordUndo = false; +}; +goog.inherits(Blockly.Events.Ui, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Ui.prototype.type = Blockly.Events.UI; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Ui.prototype.toJson = function() { + var json = Blockly.Events.Ui.superClass_.toJson.call(this); + json['element'] = this.element; + if (this.newValue !== undefined) { + json['newValue'] = this.newValue; + } + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Ui.prototype.fromJson = function(json) { + Blockly.Events.Ui.superClass_.fromJson.call(this, json); + this.element = json['element']; + this.newValue = json['newValue']; +}; + +/** + * Enable/disable a block depending on whether it is properly connected. + * Use this on applications where all blocks should be connected to a top block. + * Recommend setting the 'disable' option to 'false' in the config so that + * users don't try to reenable disabled orphan blocks. + * @param {!Blockly.Events.Abstract} event Custom data for event. + */ +Blockly.Events.disableOrphans = function(event) { + if (event.type == Blockly.Events.MOVE || + event.type == Blockly.Events.CREATE) { + Blockly.Events.disable(); + var workspace = Blockly.Workspace.getById(event.workspaceId); + var block = workspace.getBlockById(event.blockId); + if (block) { + if (block.getParent() && !block.getParent().disabled) { + var children = block.getDescendants(); + for (var i = 0, child; child = children[i]; i++) { + child.setDisabled(false); + } + } else if ((block.outputConnection || block.previousConnection) && + Blockly.dragMode_ == Blockly.DRAG_NONE) { + do { + block.setDisabled(true); + block = block.getNextBlock(); + } while (block); + } + } + Blockly.Events.enable(); + } +}; diff --git a/src/blockly/core/field.js b/src/blockly/core/field.js new file mode 100644 index 0000000..131a626 --- /dev/null +++ b/src/blockly/core/field.js @@ -0,0 +1,495 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Field. Used for editable titles, variables, etc. + * This is an abstract class that defines the UI on the block. Actual + * instances would be Blockly.FieldTextInput, Blockly.FieldDropdown, etc. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Field'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.math.Size'); +goog.require('goog.style'); +goog.require('goog.userAgent'); + + +/** + * Abstract class for an editable field. + * @param {string} text The initial content of the field. + * @param {Function=} opt_validator An optional function that is called + * to validate any constraints on what the user entered. Takes the new + * text as an argument and returns either the accepted text, a replacement + * text, or null to abort the change. + * @constructor + */ +Blockly.Field = function(text, opt_validator) { + this.size_ = new goog.math.Size(0, 25); + this.setValue(text); + this.setValidator(opt_validator); +}; + +/** + * Temporary cache of text widths. + * @type {Object} + * @private + */ +Blockly.Field.cacheWidths_ = null; + +/** + * Number of current references to cache. + * @type {number} + * @private + */ +Blockly.Field.cacheReference_ = 0; + + +/** + * Name of field. Unique within each block. + * Static labels are usually unnamed. + * @type {string=} + */ +Blockly.Field.prototype.name = undefined; + +/** + * Maximum characters of text to display before adding an ellipsis. + * @type {number} + */ +Blockly.Field.prototype.maxDisplayLength = 50; + +/** + * Visible text to display. + * @type {string} + * @private + */ +Blockly.Field.prototype.text_ = ''; + +/** + * Block this field is attached to. Starts as null, then in set in init. + * @type {Blockly.Block} + * @private + */ +Blockly.Field.prototype.sourceBlock_ = null; + +/** + * Is the field visible, or hidden due to the block being collapsed? + * @type {boolean} + * @private + */ +Blockly.Field.prototype.visible_ = true; + +/** + * Validation function called when user edits an editable field. + * @type {Function} + * @private + */ +Blockly.Field.prototype.validator_ = null; + +/** + * Non-breaking space. + * @const + */ +Blockly.Field.NBSP = '\u00A0'; + +/** + * Editable fields are saved by the XML renderer, non-editable fields are not. + */ +Blockly.Field.prototype.EDITABLE = true; + +/** + * Attach this field to a block. + * @param {!Blockly.Block} block The block containing this field. + */ +Blockly.Field.prototype.setSourceBlock = function(block) { + goog.asserts.assert(!this.sourceBlock_, 'Field already bound to a block.'); + this.sourceBlock_ = block; +}; + +/** + * Install this field on a block. + */ +Blockly.Field.prototype.init = function() { + if (this.fieldGroup_) { + // Field has already been initialized once. + return; + } + // Build the DOM. + this.fieldGroup_ = Blockly.createSvgElement('g', {}, null); + if (!this.visible_) { + this.fieldGroup_.style.display = 'none'; + } + this.borderRect_ = Blockly.createSvgElement('rect', + {'rx': 4, + 'ry': 4, + 'x': -Blockly.BlockSvg.SEP_SPACE_X / 2, + 'y': 0, + 'height': 16}, this.fieldGroup_, this.sourceBlock_.workspace); + /** @type {!Element} */ + this.textElement_ = Blockly.createSvgElement('text', + {'class': 'blocklyText', 'y': this.size_.height - 12.5}, + this.fieldGroup_); + + this.updateEditable(); + this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); + this.mouseUpWrapper_ = + Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_); + // Force a render. + this.updateTextNode_(); +}; + +/** + * Dispose of all DOM objects belonging to this editable field. + */ +Blockly.Field.prototype.dispose = function() { + if (this.mouseUpWrapper_) { + Blockly.unbindEvent_(this.mouseUpWrapper_); + this.mouseUpWrapper_ = null; + } + this.sourceBlock_ = null; + goog.dom.removeNode(this.fieldGroup_); + this.fieldGroup_ = null; + this.textElement_ = null; + this.borderRect_ = null; + this.validator_ = null; +}; + +/** + * Add or remove the UI indicating if this field is editable or not. + */ +Blockly.Field.prototype.updateEditable = function() { + var group = this.fieldGroup_; + if (!this.EDITABLE || !group) { + return; + } + if (this.sourceBlock_.isEditable()) { + Blockly.addClass_(group, 'blocklyEditableText'); + Blockly.removeClass_(group, 'blocklyNonEditableText'); + this.fieldGroup_.style.cursor = this.CURSOR; + } else { + Blockly.addClass_(group, 'blocklyNonEditableText'); + Blockly.removeClass_(group, 'blocklyEditableText'); + this.fieldGroup_.style.cursor = ''; + } +}; + +/** + * Gets whether this editable field is visible or not. + * @return {boolean} True if visible. + */ +Blockly.Field.prototype.isVisible = function() { + return this.visible_; +}; + +/** + * Sets whether this editable field is visible or not. + * @param {boolean} visible True if visible. + */ +Blockly.Field.prototype.setVisible = function(visible) { + if (this.visible_ == visible) { + return; + } + this.visible_ = visible; + var root = this.getSvgRoot(); + if (root) { + root.style.display = visible ? 'block' : 'none'; + this.render_(); + } +}; + +/** + * Sets a new validation function for editable fields. + * @param {Function} handler New validation function, or null. + */ +Blockly.Field.prototype.setValidator = function(handler) { + this.validator_ = handler; +}; + +/** + * Gets the validation function for editable fields. + * @return {Function} Validation function, or null. + */ +Blockly.Field.prototype.getValidator = function() { + return this.validator_; +}; + +/** + * Validates a change. Does nothing. Subclasses may override this. + * @param {string} text The user's text. + * @return {string} No change needed. + */ +Blockly.Field.prototype.classValidator = function(text) { + return text; +}; + +/** + * Calls the validation function for this field, as well as all the validation + * function for the field's class and its parents. + * @param {string} text Proposed text. + * @return {?string} Revised text, or null if invalid. + */ +Blockly.Field.prototype.callValidator = function(text) { + var classResult = this.classValidator(text); + if (classResult === null) { + // Class validator rejects value. Game over. + return null; + } else if (classResult !== undefined) { + text = classResult; + } + var userValidator = this.getValidator(); + if (userValidator) { + var userResult = userValidator.call(this, text); + if (userResult === null) { + // User validator rejects value. Game over. + return null; + } else if (userResult !== undefined) { + text = userResult; + } + } + return text; +}; + +/** + * Gets the group element for this editable field. + * Used for measuring the size and for positioning. + * @return {!Element} The group element. + */ +Blockly.Field.prototype.getSvgRoot = function() { + return /** @type {!Element} */ (this.fieldGroup_); +}; + +/** + * Draws the border with the correct width. + * Saves the computed width in a property. + * @private + */ +Blockly.Field.prototype.render_ = function() { + if (this.visible_ && this.textElement_) { + var key = this.textElement_.textContent + '\n' + + this.textElement_.className.baseVal; + if (Blockly.Field.cacheWidths_ && Blockly.Field.cacheWidths_[key]) { + var width = Blockly.Field.cacheWidths_[key]; + } else { + try { + var width = this.textElement_.getComputedTextLength(); + } catch (e) { + // MSIE 11 is known to throw "Unexpected call to method or property + // access." if Blockly is hidden. + var width = this.textElement_.textContent.length * 8; + } + if (Blockly.Field.cacheWidths_) { + Blockly.Field.cacheWidths_[key] = width; + } + } + if (this.borderRect_) { + this.borderRect_.setAttribute('width', + width + Blockly.BlockSvg.SEP_SPACE_X); + } + } else { + var width = 0; + } + this.size_.width = width; +}; + +/** + * Start caching field widths. Every call to this function MUST also call + * stopCache. Caches must not survive between execution threads. + */ +Blockly.Field.startCache = function() { + Blockly.Field.cacheReference_++; + if (!Blockly.Field.cacheWidths_) { + Blockly.Field.cacheWidths_ = {}; + } +}; + +/** + * Stop caching field widths. Unless caching was already on when the + * corresponding call to startCache was made. + */ +Blockly.Field.stopCache = function() { + Blockly.Field.cacheReference_--; + if (!Blockly.Field.cacheReference_) { + Blockly.Field.cacheWidths_ = null; + } +}; + +/** + * Returns the height and width of the field. + * @return {!goog.math.Size} Height and width. + */ +Blockly.Field.prototype.getSize = function() { + if (!this.size_.width) { + this.render_(); + } + return this.size_; +}; + +/** + * Returns the height and width of the field, + * accounting for the workspace scaling. + * @return {!goog.math.Size} Height and width. + * @private + */ +Blockly.Field.prototype.getScaledBBox_ = function() { + var bBox = this.borderRect_.getBBox(); + // Create new object, as getBBox can return an uneditable SVGRect in IE. + return new goog.math.Size(bBox.width * this.sourceBlock_.workspace.scale, + bBox.height * this.sourceBlock_.workspace.scale); +}; + +/** + * Get the text from this field. + * @return {string} Current text. + */ +Blockly.Field.prototype.getText = function() { + return this.text_; +}; + +/** + * Set the text in this field. Trigger a rerender of the source block. + * @param {*} text New text. + */ +Blockly.Field.prototype.setText = function(text) { + if (text === null) { + // No change if null. + return; + } + text = String(text); + if (text === this.text_) { + // No change. + return; + } + this.text_ = text; + this.updateTextNode_(); + + if (this.sourceBlock_ && this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + this.sourceBlock_.bumpNeighbours_(); + } +}; + +/** + * Update the text node of this field to display the current text. + * @private + */ +Blockly.Field.prototype.updateTextNode_ = function() { + if (!this.textElement_) { + // Not rendered yet. + return; + } + var text = this.text_; + if (text.length > this.maxDisplayLength) { + // Truncate displayed string and add an ellipsis ('...'). + text = text.substring(0, this.maxDisplayLength - 2) + '\u2026'; + } + // Empty the text element. + goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); + // Replace whitespace with non-breaking spaces so the text doesn't collapse. + text = text.replace(/\s/g, Blockly.Field.NBSP); + if (this.sourceBlock_.RTL && text) { + // The SVG is LTR, force text to be RTL. + text += '\u200F'; + } + if (!text) { + // Prevent the field from disappearing if empty. + text = Blockly.Field.NBSP; + } + var textNode = document.createTextNode(text); + this.textElement_.appendChild(textNode); + + // Cached width is obsolete. Clear it. + this.size_.width = 0; +}; + +/** + * By default there is no difference between the human-readable text and + * the language-neutral values. Subclasses (such as dropdown) may define this. + * @return {string} Current text. + */ +Blockly.Field.prototype.getValue = function() { + return this.getText(); +}; + +/** + * By default there is no difference between the human-readable text and + * the language-neutral values. Subclasses (such as dropdown) may define this. + * @param {string} newText New text. + */ +Blockly.Field.prototype.setValue = function(newText) { + if (newText === null) { + // No change if null. + return; + } + var oldText = this.getValue(); + if (oldText == newText) { + return; + } + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.Change( + this.sourceBlock_, 'field', this.name, oldText, newText)); + } + this.setText(newText); +}; + +/** + * Handle a mouse up event on an editable field. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.Field.prototype.onMouseUp_ = function(e) { + if ((goog.userAgent.IPHONE || goog.userAgent.IPAD) && + !goog.userAgent.isVersionOrHigher('537.51.2') && + e.layerX !== 0 && e.layerY !== 0) { + // Old iOS spawns a bogus event on the next touch after a 'prompt()' edit. + // Unlike the real events, these have a layerX and layerY set. + return; + } else if (Blockly.isRightButton(e)) { + // Right-click. + return; + } else if (this.sourceBlock_.workspace.isDragging()) { + // Drag operation is concluding. Don't open the editor. + return; + } else if (this.sourceBlock_.isEditable()) { + // Non-abstract sub-classes must define a showEditor_ method. + this.showEditor_(); + } +}; + +/** + * Change the tooltip text for this field. + * @param {string|!Element} newTip Text for tooltip or a parent element to + * link to for its tooltip. + */ +Blockly.Field.prototype.setTooltip = function(newTip) { + // Non-abstract sub-classes may wish to implement this. See FieldLabel. +}; + +/** + * Return the absolute coordinates of the top-left corner of this field. + * The origin (0,0) is the top-left corner of the page body. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + * @private + */ +Blockly.Field.prototype.getAbsoluteXY_ = function() { + return goog.style.getPageOffset(this.borderRect_); +}; diff --git a/src/blockly/core/field_angle.js b/src/blockly/core/field_angle.js new file mode 100644 index 0000000..a294948 --- /dev/null +++ b/src/blockly/core/field_angle.js @@ -0,0 +1,294 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Angle input field. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldAngle'); + +goog.require('Blockly.FieldTextInput'); +goog.require('goog.math'); +goog.require('goog.userAgent'); + + +/** + * Class for an editable angle field. + * @param {string} text The initial content of the field. + * @param {Function=} opt_validator An optional function that is called + * to validate any constraints on what the user entered. Takes the new + * text as an argument and returns the accepted text or null to abort + * the change. + * @extends {Blockly.FieldTextInput} + * @constructor + */ +Blockly.FieldAngle = function(text, opt_validator) { + // Add degree symbol: "360°" (LTR) or "°360" (RTL) + this.symbol_ = Blockly.createSvgElement('tspan', {}, null); + this.symbol_.appendChild(document.createTextNode('\u00B0')); + + Blockly.FieldAngle.superClass_.constructor.call(this, text, opt_validator); +}; +goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput); + +/** + * Round angles to the nearest 15 degrees when using mouse. + * Set to 0 to disable rounding. + */ +Blockly.FieldAngle.ROUND = 15; + +/** + * Half the width of protractor image. + */ +Blockly.FieldAngle.HALF = 100 / 2; + +/* The following two settings work together to set the behaviour of the angle + * picker. While many combinations are possible, two modes are typical: + * Math mode. + * 0 deg is right, 90 is up. This is the style used by protractors. + * Blockly.FieldAngle.CLOCKWISE = false; + * Blockly.FieldAngle.OFFSET = 0; + * Compass mode. + * 0 deg is up, 90 is right. This is the style used by maps. + * Blockly.FieldAngle.CLOCKWISE = true; + * Blockly.FieldAngle.OFFSET = 90; + */ + +/** + * Angle increases clockwise (true) or counterclockwise (false). + */ +Blockly.FieldAngle.CLOCKWISE = false; + +/** + * Offset the location of 0 degrees (and all angles) by a constant. + * Usually either 0 (0 = right) or 90 (0 = up). + */ +Blockly.FieldAngle.OFFSET = 0; + +/** + * Maximum allowed angle before wrapping. + * Usually either 360 (for 0 to 359.9) or 180 (for -179.9 to 180). + */ +Blockly.FieldAngle.WRAP = 360; + + +/** + * Radius of protractor circle. Slightly smaller than protractor size since + * otherwise SVG crops off half the border at the edges. + */ +Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1; + +/** + * Clean up this FieldAngle, as well as the inherited FieldTextInput. + * @return {!Function} Closure to call on destruction of the WidgetDiv. + * @private + */ +Blockly.FieldAngle.prototype.dispose_ = function() { + var thisField = this; + return function() { + Blockly.FieldAngle.superClass_.dispose_.call(thisField)(); + thisField.gauge_ = null; + if (thisField.clickWrapper_) { + Blockly.unbindEvent_(thisField.clickWrapper_); + } + if (thisField.moveWrapper1_) { + Blockly.unbindEvent_(thisField.moveWrapper1_); + } + if (thisField.moveWrapper2_) { + Blockly.unbindEvent_(thisField.moveWrapper2_); + } + }; +}; + +/** + * Show the inline free-text editor on top of the text. + * @private + */ +Blockly.FieldAngle.prototype.showEditor_ = function() { + var noFocus = + goog.userAgent.MOBILE || goog.userAgent.ANDROID || goog.userAgent.IPAD; + // Mobile browsers have issues with in-line textareas (focus & keyboards). + Blockly.FieldAngle.superClass_.showEditor_.call(this, noFocus); + var div = Blockly.WidgetDiv.DIV; + if (!div.firstChild) { + // Mobile interface uses window.prompt. + return; + } + // Build the SVG DOM. + var svg = Blockly.createSvgElement('svg', { + 'xmlns': 'http://www.w3.org/2000/svg', + 'xmlns:html': 'http://www.w3.org/1999/xhtml', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'version': '1.1', + 'height': (Blockly.FieldAngle.HALF * 2) + 'px', + 'width': (Blockly.FieldAngle.HALF * 2) + 'px' + }, div); + var circle = Blockly.createSvgElement('circle', { + 'cx': Blockly.FieldAngle.HALF, 'cy': Blockly.FieldAngle.HALF, + 'r': Blockly.FieldAngle.RADIUS, + 'class': 'blocklyAngleCircle' + }, svg); + this.gauge_ = Blockly.createSvgElement('path', + {'class': 'blocklyAngleGauge'}, svg); + this.line_ = Blockly.createSvgElement('line', + {'x1': Blockly.FieldAngle.HALF, + 'y1': Blockly.FieldAngle.HALF, + 'class': 'blocklyAngleLine'}, svg); + // Draw markers around the edge. + for (var angle = 0; angle < 360; angle += 15) { + Blockly.createSvgElement('line', { + 'x1': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS, + 'y1': Blockly.FieldAngle.HALF, + 'x2': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS - + (angle % 45 == 0 ? 10 : 5), + 'y2': Blockly.FieldAngle.HALF, + 'class': 'blocklyAngleMarks', + 'transform': 'rotate(' + angle + ',' + + Blockly.FieldAngle.HALF + ',' + Blockly.FieldAngle.HALF + ')' + }, svg); + } + svg.style.marginLeft = (15 - Blockly.FieldAngle.RADIUS) + 'px'; + this.clickWrapper_ = + Blockly.bindEvent_(svg, 'click', this, Blockly.WidgetDiv.hide); + this.moveWrapper1_ = + Blockly.bindEvent_(circle, 'mousemove', this, this.onMouseMove); + this.moveWrapper2_ = + Blockly.bindEvent_(this.gauge_, 'mousemove', this, this.onMouseMove); + this.updateGraph_(); +}; + +/** + * Set the angle to match the mouse's position. + * @param {!Event} e Mouse move event. + */ +Blockly.FieldAngle.prototype.onMouseMove = function(e) { + var bBox = this.gauge_.ownerSVGElement.getBoundingClientRect(); + var dx = e.clientX - bBox.left - Blockly.FieldAngle.HALF; + var dy = e.clientY - bBox.top - Blockly.FieldAngle.HALF; + var angle = Math.atan(-dy / dx); + if (isNaN(angle)) { + // This shouldn't happen, but let's not let this error propogate further. + return; + } + angle = goog.math.toDegrees(angle); + // 0: East, 90: North, 180: West, 270: South. + if (dx < 0) { + angle += 180; + } else if (dy > 0) { + angle += 360; + } + if (Blockly.FieldAngle.CLOCKWISE) { + angle = Blockly.FieldAngle.OFFSET + 360 - angle; + } else { + angle -= Blockly.FieldAngle.OFFSET; + } + if (Blockly.FieldAngle.ROUND) { + angle = Math.round(angle / Blockly.FieldAngle.ROUND) * + Blockly.FieldAngle.ROUND; + } + angle = this.callValidator(angle); + Blockly.FieldTextInput.htmlInput_.value = angle; + this.setValue(angle); + this.validate_(); + this.resizeEditor_(); +}; + +/** + * Insert a degree symbol. + * @param {?string} text New text. + */ +Blockly.FieldAngle.prototype.setText = function(text) { + Blockly.FieldAngle.superClass_.setText.call(this, text); + if (!this.textElement_) { + // Not rendered yet. + return; + } + this.updateGraph_(); + // Insert degree symbol. + if (this.sourceBlock_.RTL) { + this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild); + } else { + this.textElement_.appendChild(this.symbol_); + } + // Cached width is obsolete. Clear it. + this.size_.width = 0; +}; + +/** + * Redraw the graph with the current angle. + * @private + */ +Blockly.FieldAngle.prototype.updateGraph_ = function() { + if (!this.gauge_) { + return; + } + var angleDegrees = Number(this.getText()) + Blockly.FieldAngle.OFFSET; + var angleRadians = goog.math.toRadians(angleDegrees); + var path = ['M ', Blockly.FieldAngle.HALF, ',', Blockly.FieldAngle.HALF]; + var x2 = Blockly.FieldAngle.HALF; + var y2 = Blockly.FieldAngle.HALF; + if (!isNaN(angleRadians)) { + var angle1 = goog.math.toRadians(Blockly.FieldAngle.OFFSET); + var x1 = Math.cos(angle1) * Blockly.FieldAngle.RADIUS; + var y1 = Math.sin(angle1) * -Blockly.FieldAngle.RADIUS; + if (Blockly.FieldAngle.CLOCKWISE) { + angleRadians = 2 * angle1 - angleRadians; + } + x2 += Math.cos(angleRadians) * Blockly.FieldAngle.RADIUS; + y2 -= Math.sin(angleRadians) * Blockly.FieldAngle.RADIUS; + // Don't ask how the flag calculations work. They just do. + var largeFlag = Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2); + if (Blockly.FieldAngle.CLOCKWISE) { + largeFlag = 1 - largeFlag; + } + var sweepFlag = Number(Blockly.FieldAngle.CLOCKWISE); + path.push(' l ', x1, ',', y1, + ' A ', Blockly.FieldAngle.RADIUS, ',', Blockly.FieldAngle.RADIUS, + ' 0 ', largeFlag, ' ', sweepFlag, ' ', x2, ',', y2, ' z'); + } + this.gauge_.setAttribute('d', path.join('')); + this.line_.setAttribute('x2', x2); + this.line_.setAttribute('y2', y2); +}; + +/** + * Ensure that only an angle may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid angle, or null if invalid. + */ +Blockly.FieldAngle.prototype.classValidator = function(text) { + if (text === null) { + return null; + } + var n = parseFloat(text || 0); + if (isNaN(n)) { + return null; + } + n = n % 360; + if (n < 0) { + n += 360; + } + if (n > Blockly.FieldAngle.WRAP) { + n -= 360; + } + return String(n); +}; diff --git a/src/blockly/core/field_checkbox.js b/src/blockly/core/field_checkbox.js new file mode 100644 index 0000000..638aba9 --- /dev/null +++ b/src/blockly/core/field_checkbox.js @@ -0,0 +1,117 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Checkbox field. Checked or not checked. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldCheckbox'); + +goog.require('Blockly.Field'); + + +/** + * Class for a checkbox field. + * @param {string} state The initial state of the field ('TRUE' or 'FALSE'). + * @param {Function=} opt_validator A function that is executed when a new + * option is selected. Its sole argument is the new checkbox state. If + * it returns a value, this becomes the new checkbox state, unless the + * value is null, in which case the change is aborted. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldCheckbox = function(state, opt_validator) { + Blockly.FieldCheckbox.superClass_.constructor.call(this, '', opt_validator); + // Set the initial state. + this.setValue(state); +}; +goog.inherits(Blockly.FieldCheckbox, Blockly.Field); + +/** + * Character for the checkmark. + */ +Blockly.FieldCheckbox.CHECK_CHAR = '\u2713'; + +/** + * Mouse cursor style when over the hotspot that initiates editability. + */ +Blockly.FieldCheckbox.prototype.CURSOR = 'default'; + +/** + * Install this checkbox on a block. + */ +Blockly.FieldCheckbox.prototype.init = function() { + if (this.fieldGroup_) { + // Checkbox has already been initialized once. + return; + } + Blockly.FieldCheckbox.superClass_.init.call(this); + // The checkbox doesn't use the inherited text element. + // Instead it uses a custom checkmark element that is either visible or not. + this.checkElement_ = Blockly.createSvgElement('text', + {'class': 'blocklyText blocklyCheckbox', 'x': -3, 'y': 14}, + this.fieldGroup_); + var textNode = document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR); + this.checkElement_.appendChild(textNode); + this.checkElement_.style.display = this.state_ ? 'block' : 'none'; +}; + +/** + * Return 'TRUE' if the checkbox is checked, 'FALSE' otherwise. + * @return {string} Current state. + */ +Blockly.FieldCheckbox.prototype.getValue = function() { + return String(this.state_).toUpperCase(); +}; + +/** + * Set the checkbox to be checked if strBool is 'TRUE', unchecks otherwise. + * @param {string} strBool New state. + */ +Blockly.FieldCheckbox.prototype.setValue = function(strBool) { + var newState = (strBool == 'TRUE'); + if (this.state_ !== newState) { + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.Change( + this.sourceBlock_, 'field', this.name, this.state_, newState)); + } + this.state_ = newState; + if (this.checkElement_) { + this.checkElement_.style.display = newState ? 'block' : 'none'; + } + } +}; + +/** + * Toggle the state of the checkbox. + * @private + */ +Blockly.FieldCheckbox.prototype.showEditor_ = function() { + var newState = !this.state_; + if (this.sourceBlock_) { + // Call any validation function, and allow it to override. + newState = this.callValidator(newState); + } + if (newState !== null) { + this.setValue(String(newState).toUpperCase()); + } +}; diff --git a/src/blockly/core/field_colour.js b/src/blockly/core/field_colour.js new file mode 100644 index 0000000..30b7dc5 --- /dev/null +++ b/src/blockly/core/field_colour.js @@ -0,0 +1,234 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Colour input field. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldColour'); + +goog.require('Blockly.Field'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.style'); +goog.require('goog.ui.ColorPicker'); + + +/** + * Class for a colour input field. + * @param {string} colour The initial colour in '#rrggbb' format. + * @param {Function=} opt_validator A function that is executed when a new + * colour is selected. Its sole argument is the new colour value. Its + * return value becomes the selected colour, unless it is undefined, in + * which case the new colour stands, or it is null, in which case the change + * is aborted. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldColour = function(colour, opt_validator) { + Blockly.FieldColour.superClass_.constructor.call(this, colour, opt_validator); + this.setText(Blockly.Field.NBSP + Blockly.Field.NBSP + Blockly.Field.NBSP); +}; +goog.inherits(Blockly.FieldColour, Blockly.Field); + +/** + * By default use the global constants for colours. + * @type {Array.} + * @private + */ +Blockly.FieldColour.prototype.colours_ = null; + +/** + * By default use the global constants for columns. + * @type {number} + * @private + */ +Blockly.FieldColour.prototype.columns_ = 0; + +/** + * Install this field on a block. + */ +Blockly.FieldColour.prototype.init = function() { + Blockly.FieldColour.superClass_.init.call(this); + this.borderRect_.style['fillOpacity'] = 1; + this.setValue(this.getValue()); +}; + +/** + * Mouse cursor style when over the hotspot that initiates the editor. + */ +Blockly.FieldColour.prototype.CURSOR = 'default'; + +/** + * Close the colour picker if this input is being deleted. + */ +Blockly.FieldColour.prototype.dispose = function() { + Blockly.WidgetDiv.hideIfOwner(this); + Blockly.FieldColour.superClass_.dispose.call(this); +}; + +/** + * Return the current colour. + * @return {string} Current colour in '#rrggbb' format. + */ +Blockly.FieldColour.prototype.getValue = function() { + return this.colour_; +}; + +/** + * Set the colour. + * @param {string} colour The new colour in '#rrggbb' format. + */ +Blockly.FieldColour.prototype.setValue = function(colour) { + if (this.sourceBlock_ && Blockly.Events.isEnabled() && + this.colour_ != colour) { + Blockly.Events.fire(new Blockly.Events.Change( + this.sourceBlock_, 'field', this.name, this.colour_, colour)); + } + this.colour_ = colour; + if (this.borderRect_) { + this.borderRect_.style.fill = colour; + } +}; + +/** + * Get the text from this field. Used when the block is collapsed. + * @return {string} Current text. + */ +Blockly.FieldColour.prototype.getText = function() { + var colour = this.colour_; + // Try to use #rgb format if possible, rather than #rrggbb. + var m = colour.match(/^#(.)\1(.)\2(.)\3$/); + if (m) { + colour = '#' + m[1] + m[2] + m[3]; + } + return colour; +}; + +/** + * An array of colour strings for the palette. + * See bottom of this page for the default: + * http://docs.closure-library.googlecode.com/git/closure_goog_ui_colorpicker.js.source.html + * @type {!Array.} + */ +Blockly.FieldColour.COLOURS = goog.ui.ColorPicker.SIMPLE_GRID_COLORS; + +/** + * Number of columns in the palette. + */ +Blockly.FieldColour.COLUMNS = 7; + +/** + * Set a custom colour grid for this field. + * @param {Array.} colours Array of colours for this block, + * or null to use default (Blockly.FieldColour.COLOURS). + * @return {!Blockly.FieldColour} Returns itself (for method chaining). + */ +Blockly.FieldColour.prototype.setColours = function(colours) { + this.colours_ = colours; + return this; +}; + +/** + * Set a custom grid size for this field. + * @param {number} columns Number of columns for this block, + * or 0 to use default (Blockly.FieldColour.COLUMNS). + * @return {!Blockly.FieldColour} Returns itself (for method chaining). + */ +Blockly.FieldColour.prototype.setColumns = function(columns) { + this.columns_ = columns; + return this; +}; + +/** + * Create a palette under the colour field. + * @private + */ +Blockly.FieldColour.prototype.showEditor_ = function() { + Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, + Blockly.FieldColour.widgetDispose_); + // Create the palette using Closure. + var picker = new goog.ui.ColorPicker(); + picker.setSize(this.columns_ || Blockly.FieldColour.COLUMNS); + picker.setColors(this.colours_ || Blockly.FieldColour.COLOURS); + + // Position the palette to line up with the field. + // Record windowSize and scrollOffset before adding the palette. + var windowSize = goog.dom.getViewportSize(); + var scrollOffset = goog.style.getViewportPageOffset(document); + var xy = this.getAbsoluteXY_(); + var borderBBox = this.getScaledBBox_(); + var div = Blockly.WidgetDiv.DIV; + picker.render(div); + picker.setSelectedColor(this.getValue()); + // Record paletteSize after adding the palette. + var paletteSize = goog.style.getSize(picker.getElement()); + + // Flip the palette vertically if off the bottom. + if (xy.y + paletteSize.height + borderBBox.height >= + windowSize.height + scrollOffset.y) { + xy.y -= paletteSize.height - 1; + } else { + xy.y += borderBBox.height - 1; + } + if (this.sourceBlock_.RTL) { + xy.x += borderBBox.width; + xy.x -= paletteSize.width; + // Don't go offscreen left. + if (xy.x < scrollOffset.x) { + xy.x = scrollOffset.x; + } + } else { + // Don't go offscreen right. + if (xy.x > windowSize.width + scrollOffset.x - paletteSize.width) { + xy.x = windowSize.width + scrollOffset.x - paletteSize.width; + } + } + Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset, + this.sourceBlock_.RTL); + + // Configure event handler. + var thisField = this; + Blockly.FieldColour.changeEventKey_ = goog.events.listen(picker, + goog.ui.ColorPicker.EventType.CHANGE, + function(event) { + var colour = event.target.getSelectedColor() || '#000000'; + Blockly.WidgetDiv.hide(); + if (thisField.sourceBlock_) { + // Call any validation function, and allow it to override. + colour = thisField.callValidator(colour); + } + if (colour !== null) { + thisField.setValue(colour); + } + }); +}; + +/** + * Hide the colour palette. + * @private + */ +Blockly.FieldColour.widgetDispose_ = function() { + if (Blockly.FieldColour.changeEventKey_) { + goog.events.unlistenByKey(Blockly.FieldColour.changeEventKey_); + } +}; diff --git a/src/blockly/core/field_date.js b/src/blockly/core/field_date.js new file mode 100644 index 0000000..24f3239 --- /dev/null +++ b/src/blockly/core/field_date.js @@ -0,0 +1,346 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2015 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Date input field. + * @author pkendall64@gmail.com (Paul Kendall) + */ +'use strict'; + +goog.provide('Blockly.FieldDate'); + +goog.require('Blockly.Field'); +goog.require('goog.date'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.i18n.DateTimeSymbols'); +goog.require('goog.i18n.DateTimeSymbols_he'); +goog.require('goog.style'); +goog.require('goog.ui.DatePicker'); + + +/** + * Class for a date input field. + * @param {string} date The initial date. + * @param {Function=} opt_validator A function that is executed when a new + * date is selected. Its sole argument is the new date value. Its + * return value becomes the selected date, unless it is undefined, in + * which case the new date stands, or it is null, in which case the change + * is aborted. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldDate = function(date, opt_validator) { + if (!date) { + date = new goog.date.Date().toIsoString(true); + } + Blockly.FieldDate.superClass_.constructor.call(this, date, opt_validator); + this.setValue(date); +}; +goog.inherits(Blockly.FieldDate, Blockly.Field); + +/** + * Mouse cursor style when over the hotspot that initiates the editor. + */ +Blockly.FieldDate.prototype.CURSOR = 'text'; + +/** + * Close the colour picker if this input is being deleted. + */ +Blockly.FieldDate.prototype.dispose = function() { + Blockly.WidgetDiv.hideIfOwner(this); + Blockly.FieldDate.superClass_.dispose.call(this); +}; + +/** + * Return the current date. + * @return {string} Current date. + */ +Blockly.FieldDate.prototype.getValue = function() { + return this.date_; +}; + +/** + * Set the date. + * @param {string} date The new date. + */ +Blockly.FieldDate.prototype.setValue = function(date) { + if (this.sourceBlock_) { + var validated = this.callValidator(date); + // If the new date is invalid, validation returns null. + // In this case we still want to display the illegal result. + if (validated !== null) { + date = validated; + } + } + this.date_ = date; + Blockly.Field.prototype.setText.call(this, date); +}; + +/** + * Create a date picker under the date field. + * @private + */ +Blockly.FieldDate.prototype.showEditor_ = function() { + Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, + Blockly.FieldDate.widgetDispose_); + // Create the date picker using Closure. + Blockly.FieldDate.loadLanguage_(); + var picker = new goog.ui.DatePicker(); + picker.setAllowNone(false); + picker.setShowWeekNum(false); + + // Position the picker to line up with the field. + // Record windowSize and scrollOffset before adding the picker. + var windowSize = goog.dom.getViewportSize(); + var scrollOffset = goog.style.getViewportPageOffset(document); + var xy = this.getAbsoluteXY_(); + var borderBBox = this.getScaledBBox_(); + var div = Blockly.WidgetDiv.DIV; + picker.render(div); + picker.setDate(goog.date.fromIsoString(this.getValue())); + // Record pickerSize after adding the date picker. + var pickerSize = goog.style.getSize(picker.getElement()); + + // Flip the picker vertically if off the bottom. + if (xy.y + pickerSize.height + borderBBox.height >= + windowSize.height + scrollOffset.y) { + xy.y -= pickerSize.height - 1; + } else { + xy.y += borderBBox.height - 1; + } + if (this.sourceBlock_.RTL) { + xy.x += borderBBox.width; + xy.x -= pickerSize.width; + // Don't go offscreen left. + if (xy.x < scrollOffset.x) { + xy.x = scrollOffset.x; + } + } else { + // Don't go offscreen right. + if (xy.x > windowSize.width + scrollOffset.x - pickerSize.width) { + xy.x = windowSize.width + scrollOffset.x - pickerSize.width; + } + } + Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset, + this.sourceBlock_.RTL); + + // Configure event handler. + var thisField = this; + Blockly.FieldDate.changeEventKey_ = goog.events.listen(picker, + goog.ui.DatePicker.Events.CHANGE, + function(event) { + var date = event.date ? event.date.toIsoString(true) : ''; + Blockly.WidgetDiv.hide(); + if (thisField.sourceBlock_) { + // Call any validation function, and allow it to override. + date = thisField.callValidator(date); + } + thisField.setValue(date); + }); +}; + +/** + * Hide the date picker. + * @private + */ +Blockly.FieldDate.widgetDispose_ = function() { + if (Blockly.FieldDate.changeEventKey_) { + goog.events.unlistenByKey(Blockly.FieldDate.changeEventKey_); + } +}; + +/** + * Load the best language pack by scanning the Blockly.Msg object for a + * language that matches the available languages in Closure. + * @private + */ +Blockly.FieldDate.loadLanguage_ = function() { + var reg = /^DateTimeSymbols_(.+)$/; + for (var prop in goog.i18n) { + var m = prop.match(reg); + if (m) { + var lang = m[1].toLowerCase().replace('_', '.'); // E.g. 'pt.br' + if (goog.getObjectByName(lang, Blockly.Msg)) { + goog.i18n.DateTimeSymbols = goog.i18n[prop]; + } + } + } +}; + +/** + * CSS for date picker. See css.js for use. + */ +Blockly.FieldDate.CSS = [ + /* Copied from: goog/css/datepicker.css */ + /** + * Copyright 2009 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /** + * Standard styling for a goog.ui.DatePicker. + * + * @author arv@google.com (Erik Arvidsson) + */ + + '.blocklyWidgetDiv .goog-date-picker,', + '.blocklyWidgetDiv .goog-date-picker th,', + '.blocklyWidgetDiv .goog-date-picker td {', + ' font: 13px Arial, sans-serif;', + '}', + + '.blocklyWidgetDiv .goog-date-picker {', + ' -moz-user-focus: normal;', + ' -moz-user-select: none;', + ' position: relative;', + ' border: 1px solid #000;', + ' float: left;', + ' padding: 2px;', + ' color: #000;', + ' background: #c3d9ff;', + ' cursor: default;', + '}', + + '.blocklyWidgetDiv .goog-date-picker th {', + ' text-align: center;', + '}', + + '.blocklyWidgetDiv .goog-date-picker td {', + ' text-align: center;', + ' vertical-align: middle;', + ' padding: 1px 3px;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-menu {', + ' position: absolute;', + ' background: threedface;', + ' border: 1px solid gray;', + ' -moz-user-focus: normal;', + ' z-index: 1;', + ' outline: none;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-menu ul {', + ' list-style: none;', + ' margin: 0px;', + ' padding: 0px;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-menu ul li {', + ' cursor: default;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-menu-selected {', + ' background: #ccf;', + '}', + + '.blocklyWidgetDiv .goog-date-picker th {', + ' font-size: .9em;', + '}', + + '.blocklyWidgetDiv .goog-date-picker td div {', + ' float: left;', + '}', + + '.blocklyWidgetDiv .goog-date-picker button {', + ' padding: 0px;', + ' margin: 1px 0;', + ' border: 0;', + ' color: #20c;', + ' font-weight: bold;', + ' background: transparent;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-date {', + ' background: #fff;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-week,', + '.blocklyWidgetDiv .goog-date-picker-wday {', + ' padding: 1px 3px;', + ' border: 0;', + ' border-color: #a2bbdd;', + ' border-style: solid;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-week {', + ' border-right-width: 1px;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-wday {', + ' border-bottom-width: 1px;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-head td {', + ' text-align: center;', + '}', + + /** Use td.className instead of !important */ + '.blocklyWidgetDiv td.goog-date-picker-today-cont {', + ' text-align: center;', + '}', + + /** Use td.className instead of !important */ + '.blocklyWidgetDiv td.goog-date-picker-none-cont {', + ' text-align: center;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-month {', + ' min-width: 11ex;', + ' white-space: nowrap;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-year {', + ' min-width: 6ex;', + ' white-space: nowrap;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-monthyear {', + ' white-space: nowrap;', + '}', + + '.blocklyWidgetDiv .goog-date-picker table {', + ' border-collapse: collapse;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-other-month {', + ' color: #888;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-wkend-start,', + '.blocklyWidgetDiv .goog-date-picker-wkend-end {', + ' background: #eee;', + '}', + + /** Use td.className instead of !important */ + '.blocklyWidgetDiv td.goog-date-picker-selected {', + ' background: #c3d9ff;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-today {', + ' background: #9ab;', + ' font-weight: bold !important;', + ' border-color: #246 #9bd #9bd #246;', + ' color: #fff;', + '}' +]; diff --git a/src/blockly/core/field_dropdown.js b/src/blockly/core/field_dropdown.js new file mode 100644 index 0000000..ec3dd4f --- /dev/null +++ b/src/blockly/core/field_dropdown.js @@ -0,0 +1,320 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Dropdown input field. Used for editable titles and variables. + * In the interests of a consistent UI, the toolbox shares some functions and + * properties with the context menu. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldDropdown'); + +goog.require('Blockly.Field'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.style'); +goog.require('goog.ui.Menu'); +goog.require('goog.ui.MenuItem'); +goog.require('goog.userAgent'); + + +/** + * Class for an editable dropdown field. + * @param {(!Array.>|!Function)} menuGenerator An array of + * options for a dropdown list, or a function which generates these options. + * @param {Function=} opt_validator A function that is executed when a new + * option is selected, with the newly selected value as its sole argument. + * If it returns a value, that value (which must be one of the options) will + * become selected in place of the newly selected option, unless the return + * value is null, in which case the change is aborted. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldDropdown = function(menuGenerator, opt_validator) { + this.menuGenerator_ = menuGenerator; + this.trimOptions_(); + var firstTuple = this.getOptions_()[0]; + + // Call parent's constructor. + Blockly.FieldDropdown.superClass_.constructor.call(this, firstTuple[1], + opt_validator); +}; +goog.inherits(Blockly.FieldDropdown, Blockly.Field); + +/** + * Horizontal distance that a checkmark ovehangs the dropdown. + */ +Blockly.FieldDropdown.CHECKMARK_OVERHANG = 25; + +/** + * Android can't (in 2014) display "▾", so use "▼" instead. + */ +Blockly.FieldDropdown.ARROW_CHAR = goog.userAgent.ANDROID ? '\u25BC' : '\u25BE'; + +/** + * Mouse cursor style when over the hotspot that initiates the editor. + */ +Blockly.FieldDropdown.prototype.CURSOR = 'default'; + +/** + * Install this dropdown on a block. + */ +Blockly.FieldDropdown.prototype.init = function() { + if (this.fieldGroup_) { + // Dropdown has already been initialized once. + return; + } + // Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL) + this.arrow_ = Blockly.createSvgElement('tspan', {}, null); + this.arrow_.appendChild(document.createTextNode( + this.sourceBlock_.RTL ? Blockly.FieldDropdown.ARROW_CHAR + ' ' : + ' ' + Blockly.FieldDropdown.ARROW_CHAR)); + + Blockly.FieldDropdown.superClass_.init.call(this); + // Force a reset of the text to add the arrow. + var text = this.text_; + this.text_ = null; + this.setText(text); +}; + +/** + * Create a dropdown menu under the text. + * @private + */ +Blockly.FieldDropdown.prototype.showEditor_ = function() { + Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, null); + var thisField = this; + + function callback(e) { + var menuItem = e.target; + if (menuItem) { + var value = menuItem.getValue(); + if (thisField.sourceBlock_) { + // Call any validation function, and allow it to override. + value = thisField.callValidator(value); + } + if (value !== null) { + thisField.setValue(value); + } + } + Blockly.WidgetDiv.hideIfOwner(thisField); + } + + var menu = new goog.ui.Menu(); + menu.setRightToLeft(this.sourceBlock_.RTL); + var options = this.getOptions_(); + for (var i = 0; i < options.length; i++) { + var text = options[i][0]; // Human-readable text. + var value = options[i][1]; // Language-neutral value. + var menuItem = new goog.ui.MenuItem(text); + menuItem.setRightToLeft(this.sourceBlock_.RTL); + menuItem.setValue(value); + menuItem.setCheckable(true); + menu.addChild(menuItem, true); + menuItem.setChecked(value == this.value_); + } + // Listen for mouse/keyboard events. + goog.events.listen(menu, goog.ui.Component.EventType.ACTION, callback); + // Listen for touch events (why doesn't Closure handle this already?). + function callbackTouchStart(e) { + var control = this.getOwnerControl(/** @type {Node} */ (e.target)); + // Highlight the menu item. + control.handleMouseDown(e); + } + function callbackTouchEnd(e) { + var control = this.getOwnerControl(/** @type {Node} */ (e.target)); + // Activate the menu item. + control.performActionInternal(e); + } + menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHSTART, + callbackTouchStart); + menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHEND, + callbackTouchEnd); + + // Record windowSize and scrollOffset before adding menu. + var windowSize = goog.dom.getViewportSize(); + var scrollOffset = goog.style.getViewportPageOffset(document); + var xy = this.getAbsoluteXY_(); + var borderBBox = this.getScaledBBox_(); + var div = Blockly.WidgetDiv.DIV; + menu.render(div); + var menuDom = menu.getElement(); + Blockly.addClass_(menuDom, 'blocklyDropdownMenu'); + // Record menuSize after adding menu. + var menuSize = goog.style.getSize(menuDom); + // Recalculate height for the total content, not only box height. + menuSize.height = menuDom.scrollHeight; + + // Position the menu. + // Flip menu vertically if off the bottom. + if (xy.y + menuSize.height + borderBBox.height >= + windowSize.height + scrollOffset.y) { + xy.y -= menuSize.height + 2; + } else { + xy.y += borderBBox.height; + } + if (this.sourceBlock_.RTL) { + xy.x += borderBBox.width; + xy.x += Blockly.FieldDropdown.CHECKMARK_OVERHANG; + // Don't go offscreen left. + if (xy.x < scrollOffset.x + menuSize.width) { + xy.x = scrollOffset.x + menuSize.width; + } + } else { + xy.x -= Blockly.FieldDropdown.CHECKMARK_OVERHANG; + // Don't go offscreen right. + if (xy.x > windowSize.width + scrollOffset.x - menuSize.width) { + xy.x = windowSize.width + scrollOffset.x - menuSize.width; + } + } + Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset, + this.sourceBlock_.RTL); + menu.setAllowAutoFocus(true); + menuDom.focus(); +}; + +/** + * Factor out common words in statically defined options. + * Create prefix and/or suffix labels. + * @private + */ +Blockly.FieldDropdown.prototype.trimOptions_ = function() { + this.prefixField = null; + this.suffixField = null; + var options = this.menuGenerator_; + if (!goog.isArray(options) || options.length < 2) { + return; + } + var strings = options.map(function(t) {return t[0];}); + var shortest = Blockly.shortestStringLength(strings); + var prefixLength = Blockly.commonWordPrefix(strings, shortest); + var suffixLength = Blockly.commonWordSuffix(strings, shortest); + if (!prefixLength && !suffixLength) { + return; + } + if (shortest <= prefixLength + suffixLength) { + // One or more strings will entirely vanish if we proceed. Abort. + return; + } + if (prefixLength) { + this.prefixField = strings[0].substring(0, prefixLength - 1); + } + if (suffixLength) { + this.suffixField = strings[0].substr(1 - suffixLength); + } + // Remove the prefix and suffix from the options. + var newOptions = []; + for (var i = 0; i < options.length; i++) { + var text = options[i][0]; + var value = options[i][1]; + text = text.substring(prefixLength, text.length - suffixLength); + newOptions[i] = [text, value]; + } + this.menuGenerator_ = newOptions; +}; + +/** + * Return a list of the options for this dropdown. + * @return {!Array.>} Array of option tuples: + * (human-readable text, language-neutral name). + * @private + */ +Blockly.FieldDropdown.prototype.getOptions_ = function() { + if (goog.isFunction(this.menuGenerator_)) { + return this.menuGenerator_.call(this); + } + return /** @type {!Array.>} */ (this.menuGenerator_); +}; + +/** + * Get the language-neutral value from this dropdown menu. + * @return {string} Current text. + */ +Blockly.FieldDropdown.prototype.getValue = function() { + return this.value_; +}; + +/** + * Set the language-neutral value for this dropdown menu. + * @param {string} newValue New value to set. + */ +Blockly.FieldDropdown.prototype.setValue = function(newValue) { + if (newValue === null || newValue === this.value_) { + return; // No change if null. + } + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.Change( + this.sourceBlock_, 'field', this.name, this.value_, newValue)); + } + this.value_ = newValue; + // Look up and display the human-readable text. + var options = this.getOptions_(); + for (var i = 0; i < options.length; i++) { + // Options are tuples of human-readable text and language-neutral values. + if (options[i][1] == newValue) { + this.setText(options[i][0]); + return; + } + } + // Value not found. Add it, maybe it will become valid once set + // (like variable names). + this.setText(newValue); +}; + +/** + * Set the text in this field. Trigger a rerender of the source block. + * @param {?string} text New text. + */ +Blockly.FieldDropdown.prototype.setText = function(text) { + if (this.sourceBlock_ && this.arrow_) { + // Update arrow's colour. + this.arrow_.style.fill = this.sourceBlock_.getColour(); + } + if (text === null || text === this.text_) { + // No change if null. + return; + } + this.text_ = text; + this.updateTextNode_(); + + if (this.textElement_) { + // Insert dropdown arrow. + if (this.sourceBlock_.RTL) { + this.textElement_.insertBefore(this.arrow_, this.textElement_.firstChild); + } else { + this.textElement_.appendChild(this.arrow_); + } + } + + if (this.sourceBlock_ && this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + this.sourceBlock_.bumpNeighbours_(); + } +}; + +/** + * Close the dropdown menu if this input is being deleted. + */ +Blockly.FieldDropdown.prototype.dispose = function() { + Blockly.WidgetDiv.hideIfOwner(this); + Blockly.FieldDropdown.superClass_.dispose.call(this); +}; diff --git a/src/blockly/core/field_image.js b/src/blockly/core/field_image.js new file mode 100644 index 0000000..71d8052 --- /dev/null +++ b/src/blockly/core/field_image.js @@ -0,0 +1,171 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Image field. Used for titles, labels, etc. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldImage'); + +goog.require('Blockly.Field'); +goog.require('goog.dom'); +goog.require('goog.math.Size'); +goog.require('goog.userAgent'); + + +/** + * Class for an image. + * @param {string} src The URL of the image. + * @param {number} width Width of the image. + * @param {number} height Height of the image. + * @param {string=} opt_alt Optional alt text for when block is collapsed. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldImage = function(src, width, height, opt_alt) { + this.sourceBlock_ = null; + // Ensure height and width are numbers. Strings are bad at math. + this.height_ = Number(height); + this.width_ = Number(width); + this.size_ = new goog.math.Size(this.width_, + this.height_ + 2 * Blockly.BlockSvg.INLINE_PADDING_Y); + this.text_ = opt_alt || ''; + this.setValue(src); +}; +goog.inherits(Blockly.FieldImage, Blockly.Field); + +/** + * Rectangular mask used by Firefox. + * @type {Element} + * @private + */ +Blockly.FieldImage.prototype.rectElement_ = null; + +/** + * Editable fields are saved by the XML renderer, non-editable fields are not. + */ +Blockly.FieldImage.prototype.EDITABLE = false; + +/** + * Install this image on a block. + */ +Blockly.FieldImage.prototype.init = function() { + if (this.fieldGroup_) { + // Image has already been initialized once. + return; + } + // Build the DOM. + /** @type {SVGElement} */ + this.fieldGroup_ = Blockly.createSvgElement('g', {}, null); + if (!this.visible_) { + this.fieldGroup_.style.display = 'none'; + } + /** @type {SVGElement} */ + this.imageElement_ = Blockly.createSvgElement('image', + {'height': this.height_ + 'px', + 'width': this.width_ + 'px'}, this.fieldGroup_); + this.setValue(this.src_); + if (goog.userAgent.GECKO) { + /** + * Due to a Firefox bug which eats mouse events on image elements, + * a transparent rectangle needs to be placed on top of the image. + * @type {SVGElement} + */ + this.rectElement_ = Blockly.createSvgElement('rect', + {'height': this.height_ + 'px', + 'width': this.width_ + 'px', + 'fill-opacity': 0}, this.fieldGroup_); + } + this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); + + // Configure the field to be transparent with respect to tooltips. + var topElement = this.rectElement_ || this.imageElement_; + topElement.tooltip = this.sourceBlock_; + Blockly.Tooltip.bindMouseEvents(topElement); +}; + +/** + * Dispose of all DOM objects belonging to this text. + */ +Blockly.FieldImage.prototype.dispose = function() { + goog.dom.removeNode(this.fieldGroup_); + this.fieldGroup_ = null; + this.imageElement_ = null; + this.rectElement_ = null; +}; + +/** + * Change the tooltip text for this field. + * @param {string|!Element} newTip Text for tooltip or a parent element to + * link to for its tooltip. + */ +Blockly.FieldImage.prototype.setTooltip = function(newTip) { + var topElement = this.rectElement_ || this.imageElement_; + topElement.tooltip = newTip; +}; + +/** + * Get the source URL of this image. + * @return {string} Current text. + * @override + */ +Blockly.FieldImage.prototype.getValue = function() { + return this.src_; +}; + +/** + * Set the source URL of this image. + * @param {?string} src New source. + * @override + */ +Blockly.FieldImage.prototype.setValue = function(src) { + if (src === null) { + // No change if null. + return; + } + this.src_ = src; + if (this.imageElement_) { + this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink', + 'xlink:href', goog.isString(src) ? src : ''); + } +}; + +/** + * Set the alt text of this image. + * @param {?string} alt New alt text. + * @override + */ +Blockly.FieldImage.prototype.setText = function(alt) { + if (alt === null) { + // No change if null. + return; + } + this.text_ = alt; +}; + +/** + * Images are fixed width, no need to render. + * @private + */ +Blockly.FieldImage.prototype.render_ = function() { + // NOP +}; diff --git a/src/blockly/core/field_label.js b/src/blockly/core/field_label.js new file mode 100644 index 0000000..cb5fa7d --- /dev/null +++ b/src/blockly/core/field_label.js @@ -0,0 +1,104 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Non-editable text field. Used for titles, labels, etc. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldLabel'); + +goog.require('Blockly.Field'); +goog.require('Blockly.Tooltip'); +goog.require('goog.dom'); +goog.require('goog.math.Size'); + + +/** + * Class for a non-editable field. + * @param {string} text The initial content of the field. + * @param {string=} opt_class Optional CSS class for the field's text. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldLabel = function(text, opt_class) { + this.size_ = new goog.math.Size(0, 17.5); + this.class_ = opt_class; + this.setValue(text); +}; +goog.inherits(Blockly.FieldLabel, Blockly.Field); + +/** + * Editable fields are saved by the XML renderer, non-editable fields are not. + */ +Blockly.FieldLabel.prototype.EDITABLE = false; + +/** + * Install this text on a block. + */ +Blockly.FieldLabel.prototype.init = function() { + if (this.textElement_) { + // Text has already been initialized once. + return; + } + // Build the DOM. + this.textElement_ = Blockly.createSvgElement('text', + {'class': 'blocklyText', 'y': this.size_.height - 5}, null); + if (this.class_) { + Blockly.addClass_(this.textElement_, this.class_); + } + if (!this.visible_) { + this.textElement_.style.display = 'none'; + } + this.sourceBlock_.getSvgRoot().appendChild(this.textElement_); + + // Configure the field to be transparent with respect to tooltips. + this.textElement_.tooltip = this.sourceBlock_; + Blockly.Tooltip.bindMouseEvents(this.textElement_); + // Force a render. + this.updateTextNode_(); +}; + +/** + * Dispose of all DOM objects belonging to this text. + */ +Blockly.FieldLabel.prototype.dispose = function() { + goog.dom.removeNode(this.textElement_); + this.textElement_ = null; +}; + +/** + * Gets the group element for this field. + * Used for measuring the size and for positioning. + * @return {!Element} The group element. + */ +Blockly.FieldLabel.prototype.getSvgRoot = function() { + return /** @type {!Element} */ (this.textElement_); +}; + +/** + * Change the tooltip text for this field. + * @param {string|!Element} newTip Text for tooltip or a parent element to + * link to for its tooltip. + */ +Blockly.FieldLabel.prototype.setTooltip = function(newTip) { + this.textElement_.tooltip = newTip; +}; diff --git a/src/blockly/core/field_number.js b/src/blockly/core/field_number.js new file mode 100644 index 0000000..f72e7f0 --- /dev/null +++ b/src/blockly/core/field_number.js @@ -0,0 +1,101 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Number input field + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.FieldNumber'); + +goog.require('Blockly.FieldTextInput'); +goog.require('goog.math'); + +/** + * Class for an editable number field. + * @param {number|string} value The initial content of the field. + * @param {number|string|undefined} opt_min Minimum value. + * @param {number|string|undefined} opt_max Maximum value. + * @param {number|string|undefined} opt_precision Precision for value. + * @param {Function=} opt_validator An optional function that is called + * to validate any constraints on what the user entered. Takes the new + * text as an argument and returns either the accepted text, a replacement + * text, or null to abort the change. + * @extends {Blockly.FieldTextInput} + * @constructor + */ +Blockly.FieldNumber = + function(value, opt_min, opt_max, opt_precision, opt_validator) { + value = String(value); + Blockly.FieldNumber.superClass_.constructor.call(this, value, opt_validator); + this.setConstraints(opt_min, opt_max, opt_precision); +}; +goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput); + +/** + * Set the maximum, minimum and precision constraints on this field. + * Any of these properties may be undefiend or NaN to be disabled. + * Setting precision (usually a power of 10) enforces a minimum step between + * values. That is, the user's value will rounded to the closest multiple of + * precision. The least significant digit place is inferred from the precision. + * Integers values can be enforces by choosing an integer precision. + * @param {number|string|undefined} min Minimum value. + * @param {number|string|undefined} max Maximum value. + * @param {number|string|undefined} precision Precision for value. + */ +Blockly.FieldNumber.prototype.setConstraints = function(min, max, precision) { + precision = parseFloat(precision); + this.precision_ = isNaN(precision) ? 0 : precision; + min = parseFloat(min); + this.min_ = isNaN(min) ? -Infinity : min; + max = parseFloat(max); + this.max_ = isNaN(max) ? Infinity : max; + this.setValue(this.callValidator(this.getValue())); +}; + +/** + * Ensure that only a number in the correct range may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid number, or null if invalid. + */ +Blockly.FieldNumber.prototype.classValidator = function(text) { + if (text === null) { + return null; + } + text = String(text); + // TODO: Handle cases like 'ten', '1.203,14', etc. + // 'O' is sometimes mistaken for '0' by inexperienced users. + text = text.replace(/O/ig, '0'); + // Strip out thousands separators. + text = text.replace(/,/g, ''); + var n = parseFloat(text || 0); + if (isNaN(n)) { + // Invalid number. + return null; + } + // Round to nearest multiple of precision. + if (this.precision_ && isFinite(n)) { + n = Math.round(n / this.precision_) * this.precision_; + } + // Get the value in range. + n = goog.math.clamp(n, this.min_, this.max_); + return String(n); +}; diff --git a/src/blockly/core/field_textinput.js b/src/blockly/core/field_textinput.js new file mode 100644 index 0000000..5af8bf9 --- /dev/null +++ b/src/blockly/core/field_textinput.js @@ -0,0 +1,327 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Text input field. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldTextInput'); + +goog.require('Blockly.Field'); +goog.require('Blockly.Msg'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.userAgent'); + + +/** + * Class for an editable text field. + * @param {string} text The initial content of the field. + * @param {Function=} opt_validator An optional function that is called + * to validate any constraints on what the user entered. Takes the new + * text as an argument and returns either the accepted text, a replacement + * text, or null to abort the change. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldTextInput = function(text, opt_validator) { + Blockly.FieldTextInput.superClass_.constructor.call(this, text, + opt_validator); +}; +goog.inherits(Blockly.FieldTextInput, Blockly.Field); + +/** + * Point size of text. Should match blocklyText's font-size in CSS. + */ +Blockly.FieldTextInput.FONTSIZE = 11; + +/** + * Mouse cursor style when over the hotspot that initiates the editor. + */ +Blockly.FieldTextInput.prototype.CURSOR = 'text'; + +/** + * Allow browser to spellcheck this field. + * @private + */ +Blockly.FieldTextInput.prototype.spellcheck_ = true; + +/** + * Close the input widget if this input is being deleted. + */ +Blockly.FieldTextInput.prototype.dispose = function() { + Blockly.WidgetDiv.hideIfOwner(this); + Blockly.FieldTextInput.superClass_.dispose.call(this); +}; + +/** + * Set the text in this field. + * @param {?string} text New text. + * @override + */ +Blockly.FieldTextInput.prototype.setValue = function(text) { + if (text === null) { + return; // No change if null. + } + if (this.sourceBlock_) { + var validated = this.callValidator(text); + // If the new text is invalid, validation returns null. + // In this case we still want to display the illegal result. + if (validated !== null) { + text = validated; + } + } + Blockly.Field.prototype.setValue.call(this, text); +}; + +/** + * Set whether this field is spellchecked by the browser. + * @param {boolean} check True if checked. + */ +Blockly.FieldTextInput.prototype.setSpellcheck = function(check) { + this.spellcheck_ = check; +}; + +/** + * Show the inline free-text editor on top of the text. + * @param {boolean=} opt_quietInput True if editor should be created without + * focus. Defaults to false. + * @private + */ +Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) { + this.workspace_ = this.sourceBlock_.workspace; + var quietInput = opt_quietInput || false; + if (!quietInput && (goog.userAgent.MOBILE || goog.userAgent.ANDROID || + goog.userAgent.IPAD)) { + // Mobile browsers have issues with in-line textareas (focus & keyboards). + var newValue = window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_); + if (this.sourceBlock_) { + newValue = this.callValidator(newValue); + } + this.setValue(newValue); + return; + } + + Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_()); + var div = Blockly.WidgetDiv.DIV; + // Create the input. + var htmlInput = + goog.dom.createDom(goog.dom.TagName.INPUT, 'blocklyHtmlInput'); + htmlInput.setAttribute('spellcheck', this.spellcheck_); + var fontSize = + (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt'; + div.style.fontSize = fontSize; + htmlInput.style.fontSize = fontSize; + /** @type {!HTMLInputElement} */ + Blockly.FieldTextInput.htmlInput_ = htmlInput; + div.appendChild(htmlInput); + + htmlInput.value = htmlInput.defaultValue = this.text_; + htmlInput.oldValue_ = null; + this.validate_(); + this.resizeEditor_(); + if (!quietInput) { + htmlInput.focus(); + htmlInput.select(); + } + + // Bind to keydown -- trap Enter without IME and Esc to hide. + htmlInput.onKeyDownWrapper_ = + Blockly.bindEvent_(htmlInput, 'keydown', this, this.onHtmlInputKeyDown_); + // Bind to keyup -- trap Enter; resize after every keystroke. + htmlInput.onKeyUpWrapper_ = + Blockly.bindEvent_(htmlInput, 'keyup', this, this.onHtmlInputChange_); + // Bind to keyPress -- repeatedly resize when holding down a key. + htmlInput.onKeyPressWrapper_ = + Blockly.bindEvent_(htmlInput, 'keypress', this, this.onHtmlInputChange_); + htmlInput.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this); + this.workspace_.addChangeListener(htmlInput.onWorkspaceChangeWrapper_); +}; + +/** + * Handle key down to the editor. + * @param {!Event} e Keyboard event. + * @private + */ +Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) { + var htmlInput = Blockly.FieldTextInput.htmlInput_; + var tabKey = 9, enterKey = 13, escKey = 27; + if (e.keyCode == enterKey) { + Blockly.WidgetDiv.hide(); + } else if (e.keyCode == escKey) { + htmlInput.value = htmlInput.defaultValue; + Blockly.WidgetDiv.hide(); + } else if (e.keyCode == tabKey) { + Blockly.WidgetDiv.hide(); + this.sourceBlock_.tab(this, !e.shiftKey); + e.preventDefault(); + } +}; + +/** + * Handle a change to the editor. + * @param {!Event} e Keyboard event. + * @private + */ +Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(e) { + var htmlInput = Blockly.FieldTextInput.htmlInput_; + // Update source block. + var text = htmlInput.value; + if (text !== htmlInput.oldValue_) { + htmlInput.oldValue_ = text; + this.setValue(text); + this.validate_(); + } else if (goog.userAgent.WEBKIT) { + // Cursor key. Render the source block to show the caret moving. + // Chrome only (version 26, OS X). + this.sourceBlock_.render(); + } + this.resizeEditor_(); + Blockly.svgResize(this.sourceBlock_.workspace); +}; + +/** + * Check to see if the contents of the editor validates. + * Style the editor accordingly. + * @private + */ +Blockly.FieldTextInput.prototype.validate_ = function() { + var valid = true; + goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_); + var htmlInput = Blockly.FieldTextInput.htmlInput_; + if (this.sourceBlock_) { + valid = this.callValidator(htmlInput.value); + } + if (valid === null) { + Blockly.addClass_(htmlInput, 'blocklyInvalidInput'); + } else { + Blockly.removeClass_(htmlInput, 'blocklyInvalidInput'); + } +}; + +/** + * Resize the editor and the underlying block to fit the text. + * @private + */ +Blockly.FieldTextInput.prototype.resizeEditor_ = function() { + var div = Blockly.WidgetDiv.DIV; + var bBox = this.fieldGroup_.getBBox(); + div.style.width = bBox.width * this.workspace_.scale + 'px'; + div.style.height = bBox.height * this.workspace_.scale + 'px'; + var xy = this.getAbsoluteXY_(); + // In RTL mode block fields and LTR input fields the left edge moves, + // whereas the right edge is fixed. Reposition the editor. + if (this.sourceBlock_.RTL) { + var borderBBox = this.getScaledBBox_(); + xy.x += borderBBox.width; + xy.x -= div.offsetWidth; + } + // Shift by a few pixels to line up exactly. + xy.y += 1; + if (goog.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) { + // Firefox mis-reports the location of the border by a pixel + // once the WidgetDiv is moved into position. + xy.x -= 1; + xy.y -= 1; + } + if (goog.userAgent.WEBKIT) { + xy.y -= 3; + } + div.style.left = xy.x + 'px'; + div.style.top = xy.y + 'px'; +}; + +/** + * Close the editor, save the results, and dispose of the editable + * text field's elements. + * @return {!Function} Closure to call on destruction of the WidgetDiv. + * @private + */ +Blockly.FieldTextInput.prototype.widgetDispose_ = function() { + var thisField = this; + return function() { + var htmlInput = Blockly.FieldTextInput.htmlInput_; + // Save the edit (if it validates). + var text = htmlInput.value; + if (thisField.sourceBlock_) { + var text1 = thisField.callValidator(text); + if (text1 === null) { + // Invalid edit. + text = htmlInput.defaultValue; + } else { + // Validation function has changed the text. + text = text1; + if (thisField.onFinishEditing_) { + thisField.onFinishEditing_(text); + } + } + } + thisField.setValue(text); + thisField.sourceBlock_.rendered && thisField.sourceBlock_.render(); + Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_); + Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_); + Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_); + thisField.workspace_.removeChangeListener( + htmlInput.onWorkspaceChangeWrapper_); + Blockly.FieldTextInput.htmlInput_ = null; + // Delete style properties. + var style = Blockly.WidgetDiv.DIV.style; + style.width = 'auto'; + style.height = 'auto'; + style.fontSize = ''; + }; +}; + +/** + * Ensure that only a number may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid number, or null if invalid. + */ +Blockly.FieldTextInput.numberValidator = function(text) { + console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' + + 'Use Blockly.FieldNumber instead.'); + if (text === null) { + return null; + } + text = String(text); + // TODO: Handle cases like 'ten', '1.203,14', etc. + // 'O' is sometimes mistaken for '0' by inexperienced users. + text = text.replace(/O/ig, '0'); + // Strip out thousands separators. + text = text.replace(/,/g, ''); + var n = parseFloat(text || 0); + return isNaN(n) ? null : String(n); +}; + +/** + * Ensure that only a nonnegative integer may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid int, or null if invalid. + */ +Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) { + var n = Blockly.FieldTextInput.numberValidator(text); + if (n) { + n = String(Math.max(0, Math.floor(n))); + } + return n; +}; diff --git a/src/blockly/core/field_variable.js b/src/blockly/core/field_variable.js new file mode 100644 index 0000000..f93caa1 --- /dev/null +++ b/src/blockly/core/field_variable.js @@ -0,0 +1,155 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Variable input field. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldVariable'); + +goog.require('Blockly.FieldDropdown'); +goog.require('Blockly.Msg'); +goog.require('Blockly.Variables'); +goog.require('goog.string'); + + +/** + * Class for a variable's dropdown field. + * @param {?string} varname The default name for the variable. If null, + * a unique variable name will be generated. + * @param {Function=} opt_validator A function that is executed when a new + * option is selected. Its sole argument is the new option value. + * @extends {Blockly.FieldDropdown} + * @constructor + */ +Blockly.FieldVariable = function(varname, opt_validator) { + Blockly.FieldVariable.superClass_.constructor.call(this, + Blockly.FieldVariable.dropdownCreate, opt_validator); + this.setValue(varname || ''); +}; +goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown); + +/** + * Install this dropdown on a block. + */ +Blockly.FieldVariable.prototype.init = function() { + if (this.fieldGroup_) { + // Dropdown has already been initialized once. + return; + } + Blockly.FieldVariable.superClass_.init.call(this); + if (!this.getValue()) { + // Variables without names get uniquely named for this workspace. + var workspace = + this.sourceBlock_.isInFlyout ? + this.sourceBlock_.workspace.targetWorkspace : + this.sourceBlock_.workspace; + this.setValue(Blockly.Variables.generateUniqueName(workspace)); + } + // If the selected variable doesn't exist yet, create it. + // For instance, some blocks in the toolbox have variable dropdowns filled + // in by default. + if (!this.sourceBlock_.isInFlyout) { + this.sourceBlock_.workspace.createVariable(this.getValue()); + } +}; + +/** + * Get the variable's name (use a variableDB to convert into a real name). + * Unline a regular dropdown, variables are literal and have no neutral value. + * @return {string} Current text. + */ +Blockly.FieldVariable.prototype.getValue = function() { + return this.getText(); +}; + +/** + * Set the variable name. + * @param {string} newValue New text. + */ +Blockly.FieldVariable.prototype.setValue = function(newValue) { + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.Change( + this.sourceBlock_, 'field', this.name, this.value_, newValue)); + } + this.value_ = newValue; + this.setText(newValue); +}; + +/** + * Return a sorted list of variable names for variable dropdown menus. + * Include a special option at the end for creating a new variable name. + * @return {!Array.} Array of variable names. + * @this {!Blockly.FieldVariable} + */ +Blockly.FieldVariable.dropdownCreate = function() { + if (this.sourceBlock_ && this.sourceBlock_.workspace) { + // Get a copy of the list, so that adding rename and new variable options + // doesn't modify the workspace's list. + var variableList = this.sourceBlock_.workspace.variableList.slice(0); + } else { + var variableList = []; + } + // Ensure that the currently selected variable is an option. + var name = this.getText(); + if (name && variableList.indexOf(name) == -1) { + variableList.push(name); + } + variableList.sort(goog.string.caseInsensitiveCompare); + variableList.push(Blockly.Msg.RENAME_VARIABLE); + variableList.push(Blockly.Msg.DELETE_VARIABLE.replace('%1', name)); + // Variables are not language-specific, use the name as both the user-facing + // text and the internal representation. + var options = []; + for (var i = 0; i < variableList.length; i++) { + options[i] = [variableList[i], variableList[i]]; + } + return options; +}; + +/** + * Event handler for a change in variable name. + * Special case the 'Rename variable...' and 'Delete variable...' options. + * In the rename case, prompt the user for a new name. + * @param {string} text The selected dropdown menu option. + * @return {null|undefined|string} An acceptable new variable name, or null if + * change is to be either aborted (cancel button) or has been already + * handled (rename), or undefined if an existing variable was chosen. + */ +Blockly.FieldVariable.prototype.classValidator = function(text) { + var workspace = this.sourceBlock_.workspace; + if (text == Blockly.Msg.RENAME_VARIABLE) { + var oldVar = this.getText(); + Blockly.hideChaff(); + text = Blockly.Variables.promptName( + Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', oldVar), oldVar); + if (text) { + workspace.renameVariable(oldVar, text); + } + return null; + } else if (text == Blockly.Msg.DELETE_VARIABLE.replace('%1', + this.getText())) { + workspace.deleteVariable(this.getText()); + return null; + } + return undefined; +}; diff --git a/src/blockly/core/flyout.js b/src/blockly/core/flyout.js new file mode 100644 index 0000000..03ea150 --- /dev/null +++ b/src/blockly/core/flyout.js @@ -0,0 +1,1364 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Flyout tray containing blocks which may be created. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Flyout'); + +goog.require('Blockly.Block'); +goog.require('Blockly.Comment'); +goog.require('Blockly.Events'); +goog.require('Blockly.FlyoutButton'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.math.Rect'); +goog.require('goog.userAgent'); + + +/** + * Class for a flyout. + * @param {!Object} workspaceOptions Dictionary of options for the workspace. + * @constructor + */ +Blockly.Flyout = function(workspaceOptions) { + workspaceOptions.getMetrics = this.getMetrics_.bind(this); + workspaceOptions.setMetrics = this.setMetrics_.bind(this); + /** + * @type {!Blockly.Workspace} + * @private + */ + this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); + this.workspace_.isFlyout = true; + + /** + * Is RTL vs LTR. + * @type {boolean} + */ + this.RTL = !!workspaceOptions.RTL; + + /** + * Flyout should be laid out horizontally vs vertically. + * @type {boolean} + * @private + */ + this.horizontalLayout_ = workspaceOptions.horizontalLayout; + + /** + * Position of the toolbox and flyout relative to the workspace. + * @type {number} + * @private + */ + this.toolboxPosition_ = workspaceOptions.toolboxPosition; + + /** + * Opaque data that can be passed to Blockly.unbindEvent_. + * @type {!Array.} + * @private + */ + this.eventWrappers_ = []; + + /** + * List of background buttons that lurk behind each block to catch clicks + * landing in the blocks' lakes and bays. + * @type {!Array.} + * @private + */ + this.backgroundButtons_ = []; + + /** + * List of visible buttons. + * @type {!Array.} + * @private + */ + this.buttons_ = []; + + /** + * List of event listeners. + * @type {!Array.} + * @private + */ + this.listeners_ = []; + + /** + * List of blocks that should always be disabled. + * @type {!Array.} + * @private + */ + this.permanentlyDisabled_ = []; + + /** + * y coordinate of mousedown - used to calculate scroll distances. + * @private {number} + */ + this.startDragMouseY_ = 0; + + /** + * x coordinate of mousedown - used to calculate scroll distances. + * @private {number} + */ + this.startDragMouseX_ = 0; +}; + +/** + * When a flyout drag is in progress, this is a reference to the flyout being + * dragged. This is used by Flyout.terminateDrag_ to reset dragMode_. + * @private {Blockly.Flyout} + */ +Blockly.Flyout.startFlyout_ = null; + +/** + * Event that started a drag. Used to determine the drag distance/direction and + * also passed to BlockSvg.onMouseDown_() after creating a new block. + * @private {Event} + */ +Blockly.Flyout.startDownEvent_ = null; + +/** + * Flyout block where the drag/click was initiated. Used to fire click events or + * create a new block. + * @private {Event} + */ +Blockly.Flyout.startBlock_ = null; + +/** + * Wrapper function called when a mouseup occurs during a background or block + * drag operation. + * @private {Array.} + */ +Blockly.Flyout.onMouseUpWrapper_ = null; + +/** + * Wrapper function called when a mousemove occurs during a background drag. + * @private {Array.} + */ +Blockly.Flyout.onMouseMoveWrapper_ = null; + +/** + * Wrapper function called when a mousemove occurs during a block drag. + * @private {Array.} + */ +Blockly.Flyout.onMouseMoveBlockWrapper_ = null; + +/** + * Does the flyout automatically close when a block is created? + * @type {boolean} + */ +Blockly.Flyout.prototype.autoClose = true; + +/** + * Corner radius of the flyout background. + * @type {number} + * @const + */ +Blockly.Flyout.prototype.CORNER_RADIUS = 8; + +/** + * Number of pixels the mouse must move before a drag/scroll starts. Because the + * drag-intention is determined when this is reached, it is larger than + * Blockly.DRAG_RADIUS so that the drag-direction is clearer. + */ +Blockly.Flyout.prototype.DRAG_RADIUS = 10; + +/** + * Margin around the edges of the blocks in the flyout. + * @type {number} + * @const + */ +Blockly.Flyout.prototype.MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS; + +/** + * Gap between items in horizontal flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ +Blockly.Flyout.prototype.GAP_X = Blockly.Flyout.prototype.MARGIN * 3; + +/** + * Gap between items in vertical flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ +Blockly.Flyout.prototype.GAP_Y = Blockly.Flyout.prototype.MARGIN * 3; + +/** + * Top/bottom padding between scrollbar and edge of flyout background. + * @type {number} + * @const + */ +Blockly.Flyout.prototype.SCROLLBAR_PADDING = 2; + +/** + * Width of flyout. + * @type {number} + * @private + */ +Blockly.Flyout.prototype.width_ = 0; + +/** + * Height of flyout. + * @type {number} + * @private + */ +Blockly.Flyout.prototype.height_ = 0; + +/** + * Is the flyout dragging (scrolling)? + * DRAG_NONE - no drag is ongoing or state is undetermined. + * DRAG_STICKY - still within the sticky drag radius. + * DRAG_FREE - in scroll mode (never create a new block). + * @private + */ +Blockly.Flyout.prototype.dragMode_ = Blockly.DRAG_NONE; + +/** + * Range of a drag angle from a flyout considered "dragging toward workspace". + * Drags that are within the bounds of this many degrees from the orthogonal + * line to the flyout edge are considered to be "drags toward the workspace". + * Example: + * Flyout Edge Workspace + * [block] / <-within this angle, drags "toward workspace" | + * [block] ---- orthogonal to flyout boundary ---- | + * [block] \ | + * The angle is given in degrees from the orthogonal. + * + * This is used to know when to create a new block and when to scroll the + * flyout. Setting it to 360 means that all drags create a new block. + * @type {number} + * @private +*/ +Blockly.Flyout.prototype.dragAngleRange_ = 70; + +/** + * Creates the flyout's DOM. Only needs to be called once. + * @return {!Element} The flyout's SVG group. + */ +Blockly.Flyout.prototype.createDom = function() { + /* + + + + + */ + this.svgGroup_ = Blockly.createSvgElement('g', + {'class': 'blocklyFlyout'}, null); + this.svgBackground_ = Blockly.createSvgElement('path', + {'class': 'blocklyFlyoutBackground'}, this.svgGroup_); + this.svgGroup_.appendChild(this.workspace_.createDom()); + return this.svgGroup_; +}; + +/** + * Initializes the flyout. + * @param {!Blockly.Workspace} targetWorkspace The workspace in which to create + * new blocks. + */ +Blockly.Flyout.prototype.init = function(targetWorkspace) { + this.targetWorkspace_ = targetWorkspace; + this.workspace_.targetWorkspace = targetWorkspace; + // Add scrollbar. + this.scrollbar_ = new Blockly.Scrollbar(this.workspace_, + this.horizontalLayout_, false); + + this.hide(); + + Array.prototype.push.apply(this.eventWrappers_, + Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.wheel_)); + if (!this.autoClose) { + this.filterWrapper_ = this.filterForCapacity_.bind(this); + this.targetWorkspace_.addChangeListener(this.filterWrapper_); + } + // Dragging the flyout up and down. + Array.prototype.push.apply(this.eventWrappers_, + Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_)); +}; + +/** + * Dispose of this flyout. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Flyout.prototype.dispose = function() { + this.hide(); + Blockly.unbindEvent_(this.eventWrappers_); + if (this.filterWrapper_) { + this.targetWorkspace_.removeChangeListener(this.filterWrapper_); + this.filterWrapper_ = null; + } + if (this.scrollbar_) { + this.scrollbar_.dispose(); + this.scrollbar_ = null; + } + if (this.workspace_) { + this.workspace_.targetWorkspace = null; + this.workspace_.dispose(); + this.workspace_ = null; + } + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.svgBackground_ = null; + this.targetWorkspace_ = null; +}; + +/** + * Get the width of the flyout. + * @return {number} The width of the flyout. + */ +Blockly.Flyout.prototype.getWidth = function() { + return this.width_; +}; + +/** + * Get the height of the flyout. + * @return {number} The width of the flyout. + */ +Blockly.Flyout.prototype.getHeight = function() { + return this.height_; +}; + +/** + * Return an object with all the metrics required to size scrollbars for the + * flyout. The following properties are computed: + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the contents, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .absoluteTop: Top-edge of view. + * .viewLeft: Offset of the left edge of visible rectangle from parent, + * .contentLeft: Offset of the left-most content from the x=0 coordinate, + * .absoluteLeft: Left-edge of view. + * @return {Object} Contains size and position metrics of the flyout. + * @private + */ +Blockly.Flyout.prototype.getMetrics_ = function() { + if (!this.isVisible()) { + // Flyout is hidden. + return null; + } + + try { + var optionBox = this.workspace_.getCanvas().getBBox(); + } catch (e) { + // Firefox has trouble with hidden elements (Bug 528969). + var optionBox = {height: 0, y: 0, width: 0, x: 0}; + } + + var absoluteTop = this.SCROLLBAR_PADDING; + var absoluteLeft = this.SCROLLBAR_PADDING; + if (this.horizontalLayout_) { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + absoluteTop = 0; + } + var viewHeight = this.height_; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + viewHeight += this.MARGIN - this.SCROLLBAR_PADDING; + } + var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING; + } else { + absoluteLeft = 0; + var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING; + var viewWidth = this.width_; + if (!this.RTL) { + viewWidth -= this.SCROLLBAR_PADDING; + } + } + + var metrics = { + viewHeight: viewHeight, + viewWidth: viewWidth, + contentHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale, + contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale, + viewTop: -this.workspace_.scrollY, + viewLeft: -this.workspace_.scrollX, + contentTop: optionBox.y, + contentLeft: optionBox.x, + absoluteTop: absoluteTop, + absoluteLeft: absoluteLeft + }; + return metrics; +}; + +/** + * Sets the translation of the flyout to match the scrollbars. + * @param {!Object} xyRatio Contains a y property which is a float + * between 0 and 1 specifying the degree of scrolling and a + * similar x property. + * @private + */ +Blockly.Flyout.prototype.setMetrics_ = function(xyRatio) { + var metrics = this.getMetrics_(); + // This is a fix to an apparent race condition. + if (!metrics) { + return; + } + if (!this.horizontalLayout_ && goog.isNumber(xyRatio.y)) { + this.workspace_.scrollY = -metrics.contentHeight * xyRatio.y; + } else if (this.horizontalLayout_ && goog.isNumber(xyRatio.x)) { + this.workspace_.scrollX = -metrics.contentWidth * xyRatio.x; + } + + this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft, + this.workspace_.scrollY + metrics.absoluteTop); +}; + +/** + * Move the flyout to the edge of the workspace. + */ +Blockly.Flyout.prototype.position = function() { + if (!this.isVisible()) { + return; + } + var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics(); + if (!targetWorkspaceMetrics) { + // Hidden components will return null. + return; + } + var edgeWidth = this.horizontalLayout_ ? + targetWorkspaceMetrics.viewWidth : this.width_; + edgeWidth -= this.CORNER_RADIUS; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { + edgeWidth *= -1; + } + + this.setBackgroundPath_(edgeWidth, + this.horizontalLayout_ ? this.height_ : + targetWorkspaceMetrics.viewHeight); + + var x = targetWorkspaceMetrics.absoluteLeft; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { + x += targetWorkspaceMetrics.viewWidth; + x -= this.width_; + } + + var y = targetWorkspaceMetrics.absoluteTop; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + y += targetWorkspaceMetrics.viewHeight; + y -= this.height_; + } + + this.svgGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')'); + + // Record the height for Blockly.Flyout.getMetrics_, or width if the layout is + // horizontal. + if (this.horizontalLayout_) { + this.width_ = targetWorkspaceMetrics.viewWidth; + } else { + this.height_ = targetWorkspaceMetrics.viewHeight; + } + + // Update the scrollbar (if one exists). + if (this.scrollbar_) { + this.scrollbar_.resize(); + } +}; + +/** + * Create and set the path for the visible boundaries of the flyout. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ +Blockly.Flyout.prototype.setBackgroundPath_ = function(width, height) { + if (this.horizontalLayout_) { + this.setBackgroundPathHorizontal_(width, height); + } else { + this.setBackgroundPathVertical_(width, height); + } +}; + +/** + * Create and set the path for the visible boundaries of the flyout in vertical + * mode. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ +Blockly.Flyout.prototype.setBackgroundPathVertical_ = function(width, height) { + var atRight = this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT; + // Decide whether to start on the left or right. + var path = ['M ' + (atRight ? this.width_ : 0) + ',0']; + // Top. + path.push('h', width); + // Rounded corner. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, + atRight ? 0 : 1, + atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, + this.CORNER_RADIUS); + // Side closest to workspace. + path.push('v', Math.max(0, height - this.CORNER_RADIUS * 2)); + // Rounded corner. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, + atRight ? 0 : 1, + atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, + this.CORNER_RADIUS); + // Bottom. + path.push('h', -width); + path.push('z'); + this.svgBackground_.setAttribute('d', path.join(' ')); +}; + +/** + * Create and set the path for the visible boundaries of the flyout in + * horizontal mode. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ +Blockly.Flyout.prototype.setBackgroundPathHorizontal_ = function(width, + height) { + var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP; + // Start at top left. + var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)]; + + if (atTop) { + // Top. + path.push('h', width + this.CORNER_RADIUS); + // Right. + path.push('v', height); + // Bottom. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + -this.CORNER_RADIUS, this.CORNER_RADIUS); + path.push('h', -1 * (width - this.CORNER_RADIUS)); + // Left. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + -this.CORNER_RADIUS, -this.CORNER_RADIUS); + path.push('z'); + } else { + // Top. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + this.CORNER_RADIUS, -this.CORNER_RADIUS); + path.push('h', width - this.CORNER_RADIUS); + // Right. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + this.CORNER_RADIUS, this.CORNER_RADIUS); + path.push('v', height - this.CORNER_RADIUS); + // Bottom. + path.push('h', -width - this.CORNER_RADIUS); + // Left. + path.push('z'); + } + this.svgBackground_.setAttribute('d', path.join(' ')); +}; + +/** + * Scroll the flyout to the top. + */ +Blockly.Flyout.prototype.scrollToStart = function() { + this.scrollbar_.set((this.horizontalLayout_ && this.RTL) ? Infinity : 0); +}; + +/** + * Scroll the flyout. + * @param {!Event} e Mouse wheel scroll event. + * @private + */ +Blockly.Flyout.prototype.wheel_ = function(e) { + var delta = this.horizontalLayout_ ? e.deltaX : e.deltaY; + + if (delta) { + if (goog.userAgent.GECKO) { + // Firefox's deltas are a tenth that of Chrome/Safari. + delta *= 10; + } + var metrics = this.getMetrics_(); + var pos = this.horizontalLayout_ ? metrics.viewLeft + delta : + metrics.viewTop + delta; + var limit = this.horizontalLayout_ ? + metrics.contentWidth - metrics.viewWidth : + metrics.contentHeight - metrics.viewHeight; + pos = Math.min(pos, limit); + pos = Math.max(pos, 0); + this.scrollbar_.set(pos); + } + + // Don't scroll the page. + e.preventDefault(); + // Don't propagate mousewheel event (zooming). + e.stopPropagation(); +}; + +/** + * Is the flyout visible? + * @return {boolean} True if visible. + */ +Blockly.Flyout.prototype.isVisible = function() { + return this.svgGroup_ && this.svgGroup_.style.display == 'block'; +}; + +/** + * Hide and empty the flyout. + */ +Blockly.Flyout.prototype.hide = function() { + if (!this.isVisible()) { + return; + } + this.svgGroup_.style.display = 'none'; + // Delete all the event listeners. + for (var x = 0, listen; listen = this.listeners_[x]; x++) { + Blockly.unbindEvent_(listen); + } + this.listeners_.length = 0; + if (this.reflowWrapper_) { + this.workspace_.removeChangeListener(this.reflowWrapper_); + this.reflowWrapper_ = null; + } + // Do NOT delete the blocks here. Wait until Flyout.show. + // https://neil.fraser.name/news/2014/08/09/ +}; + +/** + * Show and populate the flyout. + * @param {!Array|string} xmlList List of blocks to show. + * Variables and procedures have a custom set of blocks. + */ +Blockly.Flyout.prototype.show = function(xmlList) { + this.hide(); + this.clearOldBlocks_(); + + if (xmlList == Blockly.Variables.NAME_TYPE) { + // Special category for variables. + xmlList = + Blockly.Variables.flyoutCategory(this.workspace_.targetWorkspace); + } else if (xmlList == Blockly.Procedures.NAME_TYPE) { + // Special category for procedures. + xmlList = + Blockly.Procedures.flyoutCategory(this.workspace_.targetWorkspace); + } + + this.svgGroup_.style.display = 'block'; + // Create the blocks to be shown in this flyout. + var contents = []; + var gaps = []; + this.permanentlyDisabled_.length = 0; + for (var i = 0, xml; xml = xmlList[i]; i++) { + if (xml.tagName) { + var tagName = xml.tagName.toUpperCase(); + var default_gap = this.horizontalLayout_ ? this.GAP_X : this.GAP_Y; + if (tagName == 'BLOCK') { + var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_); + if (curBlock.disabled) { + // Record blocks that were initially disabled. + // Do not enable these blocks as a result of capacity filtering. + this.permanentlyDisabled_.push(curBlock); + } + contents.push({type: 'block', block: curBlock}); + var gap = parseInt(xml.getAttribute('gap'), 10); + gaps.push(isNaN(gap) ? default_gap : gap); + } else if (xml.tagName.toUpperCase() == 'SEP') { + // Change the gap between two blocks. + // + // The default gap is 24, can be set larger or smaller. + // This overwrites the gap attribute on the previous block. + // Note that a deprecated method is to add a gap to a block. + // + var newGap = parseInt(xml.getAttribute('gap'), 10); + // Ignore gaps before the first block. + if (!isNaN(newGap) && gaps.length > 0) { + gaps[gaps.length - 1] = newGap; + } else { + gaps.push(default_gap); + } + } else if (tagName == 'BUTTON') { + var label = xml.getAttribute('text'); + var curButton = new Blockly.FlyoutButton(this.workspace_, + this.targetWorkspace_, label); + contents.push({type: 'button', button: curButton}); + gaps.push(default_gap); + } + } + } + + this.layout_(contents, gaps); + + // IE 11 is an incompetent browser that fails to fire mouseout events. + // When the mouse is over the background, deselect all blocks. + var deselectAll = function() { + var topBlocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = topBlocks[i]; i++) { + block.removeSelect(); + } + }; + + this.listeners_.push(Blockly.bindEvent_(this.svgBackground_, 'mouseover', + this, deselectAll)); + + if (this.horizontalLayout_) { + this.height_ = 0; + } else { + this.width_ = 0; + } + this.reflow(); + + this.filterForCapacity_(); + + // Correctly position the flyout's scrollbar when it opens. + this.position(); + + this.reflowWrapper_ = this.reflow.bind(this); + this.workspace_.addChangeListener(this.reflowWrapper_); +}; + +/** + * Lay out the blocks in the flyout. + * @param {!Array.} contents The blocks and buttons to lay out. + * @param {!Array.} gaps The visible gaps between blocks. + * @private + */ +Blockly.Flyout.prototype.layout_ = function(contents, gaps) { + this.workspace_.scale = this.targetWorkspace_.scale; + var margin = this.MARGIN; + var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH; + var cursorY = margin; + if (this.horizontalLayout_ && this.RTL) { + contents = contents.reverse(); + } + + for (var i = 0, item; item = contents[i]; i++) { + if (item.type == 'block') { + var block = item.block; + var allBlocks = block.getDescendants(); + for (var j = 0, child; child = allBlocks[j]; j++) { + // Mark blocks as being inside a flyout. This is used to detect and + // prevent the closure of the flyout if the user right-clicks on such a + // block. + child.isInFlyout = true; + } + block.render(); + var root = block.getSvgRoot(); + var blockHW = block.getHeightWidth(); + var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; + if (this.horizontalLayout_) { + cursorX += tab; + } + block.moveBy((this.horizontalLayout_ && this.RTL) ? + cursorX + blockHW.width - tab : cursorX, + cursorY); + if (this.horizontalLayout_) { + cursorX += (blockHW.width + gaps[i] - tab); + } else { + cursorY += blockHW.height + gaps[i]; + } + + // Create an invisible rectangle under the block to act as a button. Just + // using the block as a button is poor, since blocks have holes in them. + var rect = Blockly.createSvgElement('rect', {'fill-opacity': 0}, null); + rect.tooltip = block; + Blockly.Tooltip.bindMouseEvents(rect); + // Add the rectangles under the blocks, so that the blocks' tooltips work. + this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot()); + block.flyoutRect_ = rect; + this.backgroundButtons_[i] = rect; + + this.addBlockListeners_(root, block, rect); + } else if (item.type == 'button') { + var button = item.button; + var buttonSvg = button.createDom(); + button.moveTo(cursorX, cursorY); + button.show(); + Blockly.bindEvent_(buttonSvg, 'mouseup', button, button.onMouseUp); + + this.buttons_.push(button); + if (this.horizontalLayout_) { + cursorX += (button.width + gaps[i]); + } else { + cursorY += button.height + gaps[i]; + } + } + } +}; + +/** + * Delete blocks and background buttons from a previous showing of the flyout. + * @private + */ +Blockly.Flyout.prototype.clearOldBlocks_ = function() { + // Delete any blocks from a previous showing. + var oldBlocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = oldBlocks[i]; i++) { + if (block.workspace == this.workspace_) { + block.dispose(false, false); + } + } + // Delete any background buttons from a previous showing. + for (var j = 0, rect; rect = this.backgroundButtons_[j]; j++) { + goog.dom.removeNode(rect); + } + this.backgroundButtons_.length = 0; + + for (var i = 0, button; button = this.buttons_[i]; i++) { + button.dispose(); + } + this.buttons_.length = 0; +}; + +/** + * Add listeners to a block that has been added to the flyout. + * @param {!Element} root The root node of the SVG group the block is in. + * @param {!Blockly.Block} block The block to add listeners for. + * @param {!Element} rect The invisible rectangle under the block that acts as + * a button for that block. + * @private + */ +Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) { + this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, + this.blockMouseDown_(block))); + this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, + this.blockMouseDown_(block))); + this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block, + block.addSelect)); + this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block, + block.removeSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block, + block.addSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block, + block.removeSelect)); +}; + +/** + * Handle a mouse-down on an SVG block in a non-closing flyout. + * @param {!Blockly.Block} block The flyout block to copy. + * @return {!Function} Function to call when block is clicked. + * @private + */ +Blockly.Flyout.prototype.blockMouseDown_ = function(block) { + var flyout = this; + return function(e) { + Blockly.terminateDrag_(); + Blockly.hideChaff(true); + if (Blockly.isRightButton(e)) { + // Right-click. + block.showContextMenu_(e); + } else { + // Left-click (or middle click) + Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); + // Record the current mouse position. + flyout.startDragMouseY_ = e.clientY; + flyout.startDragMouseX_ = e.clientX; + Blockly.Flyout.startDownEvent_ = e; + Blockly.Flyout.startBlock_ = block; + Blockly.Flyout.startFlyout_ = flyout; + Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document, + 'mouseup', flyout, flyout.onMouseUp_); + Blockly.Flyout.onMouseMoveBlockWrapper_ = Blockly.bindEvent_(document, + 'mousemove', flyout, flyout.onMouseMoveBlock_); + } + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); + e.preventDefault(); + }; +}; + +/** + * Mouse down on the flyout background. Start a vertical scroll drag. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Flyout.prototype.onMouseDown_ = function(e) { + if (Blockly.isRightButton(e)) { + return; + } + Blockly.hideChaff(true); + this.dragMode_ = Blockly.DRAG_FREE; + this.startDragMouseY_ = e.clientY; + this.startDragMouseX_ = e.clientX; + Blockly.Flyout.startFlyout_ = this; + Blockly.Flyout.onMouseMoveWrapper_ = Blockly.bindEvent_(document, 'mousemove', + this, this.onMouseMove_); + Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document, 'mouseup', + this, Blockly.Flyout.terminateDrag_); + // This event has been handled. No need to bubble up to the document. + e.preventDefault(); + e.stopPropagation(); +}; + +/** + * Handle a mouse-up anywhere in the SVG pane. Is only registered when a + * block is clicked. We can't use mouseUp on the block since a fast-moving + * cursor can briefly escape the block before it catches up. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.Flyout.prototype.onMouseUp_ = function(e) { + if (!this.workspace_.isDragging()) { + if (this.autoClose) { + this.createBlockFunc_(Blockly.Flyout.startBlock_)( + Blockly.Flyout.startDownEvent_); + } else if (!Blockly.WidgetDiv.isVisible()) { + Blockly.Events.fire( + new Blockly.Events.Ui(Blockly.Flyout.startBlock_, 'click', + undefined, undefined)); + } + } + Blockly.terminateDrag_(); +}; + +/** + * Handle a mouse-move to vertically drag the flyout. + * @param {!Event} e Mouse move event. + * @private + */ +Blockly.Flyout.prototype.onMouseMove_ = function(e) { + var metrics = this.getMetrics_(); + if (this.horizontalLayout_) { + if (metrics.contentWidth - metrics.viewWidth < 0) { + return; + } + var dx = e.clientX - this.startDragMouseX_; + this.startDragMouseX_ = e.clientX; + var x = metrics.viewLeft - dx; + x = goog.math.clamp(x, 0, metrics.contentWidth - metrics.viewWidth); + this.scrollbar_.set(x); + } else { + if (metrics.contentHeight - metrics.viewHeight < 0) { + return; + } + var dy = e.clientY - this.startDragMouseY_; + this.startDragMouseY_ = e.clientY; + var y = metrics.viewTop - dy; + y = goog.math.clamp(y, 0, metrics.contentHeight - metrics.viewHeight); + this.scrollbar_.set(y); + } +}; + +/** + * Mouse button is down on a block in a non-closing flyout. Create the block + * if the mouse moves beyond a small radius. This allows one to play with + * fields without instantiating blocks that instantly self-destruct. + * @param {!Event} e Mouse move event. + * @private + */ +Blockly.Flyout.prototype.onMouseMoveBlock_ = function(e) { + if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 && + e.button == 0) { + /* HACK: + Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove events + on certain touch actions. Ignore events with these signatures. + This may result in a one-pixel blind spot in other browsers, + but this shouldn't be noticeable. */ + e.stopPropagation(); + return; + } + var dx = e.clientX - Blockly.Flyout.startDownEvent_.clientX; + var dy = e.clientY - Blockly.Flyout.startDownEvent_.clientY; + + var createBlock = this.determineDragIntention_(dx, dy); + if (createBlock) { + this.createBlockFunc_(Blockly.Flyout.startBlock_)( + Blockly.Flyout.startDownEvent_); + } else if (this.dragMode_ == Blockly.DRAG_FREE) { + // Do a scroll. + this.onMouseMove_(e); + } + e.stopPropagation(); +}; + +/** + * Determine the intention of a drag. + * Updates dragMode_ based on a drag delta and the current mode, + * and returns true if we should create a new block. + * @param {number} dx X delta of the drag. + * @param {number} dy Y delta of the drag. + * @return {boolean} True if a new block should be created. + * @private + */ +Blockly.Flyout.prototype.determineDragIntention_ = function(dx, dy) { + if (this.dragMode_ == Blockly.DRAG_FREE) { + // Once in free mode, always stay in free mode and never create a block. + return false; + } + var dragDistance = Math.sqrt(dx * dx + dy * dy); + if (dragDistance < this.DRAG_RADIUS) { + // Still within the sticky drag radius. + this.dragMode_ = Blockly.DRAG_STICKY; + return false; + } else { + if (this.isDragTowardWorkspace_(dx, dy) || !this.scrollbar_.isVisible()) { + // Immediately create a block. + return true; + } else { + // Immediately move to free mode - the drag is away from the workspace. + this.dragMode_ = Blockly.DRAG_FREE; + return false; + } + } +}; + +/** + * Determine if a drag delta is toward the workspace, based on the position + * and orientation of the flyout. This is used in determineDragIntention_ to + * determine if a new block should be created or if the flyout should scroll. + * @param {number} dx X delta of the drag. + * @param {number} dy Y delta of the drag. + * @return {boolean} true if the drag is toward the workspace. + * @private + */ +Blockly.Flyout.prototype.isDragTowardWorkspace_ = function(dx, dy) { + // Direction goes from -180 to 180, with 0 toward the right and 90 on top. + var dragDirection = Math.atan2(dy, dx) / Math.PI * 180; + + var draggingTowardWorkspace = false; + var range = this.dragAngleRange_; + if (this.horizontalLayout_) { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + // Horizontal at top. + if (dragDirection < 90 + range && dragDirection > 90 - range) { + draggingTowardWorkspace = true; + } + } else { + // Horizontal at bottom. + if (dragDirection > -90 - range && dragDirection < -90 + range) { + draggingTowardWorkspace = true; + } + } + } else { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + // Vertical at left. + if (dragDirection < range && dragDirection > -range) { + draggingTowardWorkspace = true; + } + } else { + // Vertical at right. + if (dragDirection < -180 + range || dragDirection > 180 - range) { + draggingTowardWorkspace = true; + } + } + } + return draggingTowardWorkspace; +}; + +/** + * Create a copy of this block on the workspace. + * @param {!Blockly.Block} originBlock The flyout block to copy. + * @return {!Function} Function to call when block is clicked. + * @private + */ +Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) { + var flyout = this; + return function(e) { + if (Blockly.isRightButton(e)) { + // Right-click. Don't create a block, let the context menu show. + return; + } + if (originBlock.disabled) { + // Beyond capacity. + return; + } + Blockly.Events.disable(); + try { + var block = flyout.placeNewBlock_(originBlock); + } finally { + Blockly.Events.enable(); + } + if (Blockly.Events.isEnabled()) { + Blockly.Events.setGroup(true); + Blockly.Events.fire(new Blockly.Events.Create(block)); + } + if (flyout.autoClose) { + flyout.hide(); + } else { + flyout.filterForCapacity_(); + } + // Start a dragging operation on the new block. + block.onMouseDown_(e); + Blockly.dragMode_ = Blockly.DRAG_FREE; + block.setDragging_(true); + }; +}; + +/** + * Copy a block from the flyout to the workspace and position it correctly. + * @param {!Blockly.Block} originBlock The flyout block to copy.. + * @return {!Blockly.Block} The new block in the main workspace. + * @private + */ +Blockly.Flyout.prototype.placeNewBlock_ = function(originBlock) { + var targetWorkspace = this.targetWorkspace_; + var svgRootOld = originBlock.getSvgRoot(); + if (!svgRootOld) { + throw 'originBlock is not rendered.'; + } + // Figure out where the original block is on the screen, relative to the upper + // left corner of the main workspace. + var xyOld = Blockly.getSvgXY_(svgRootOld, targetWorkspace); + // Take into account that the flyout might have been scrolled horizontally + // (separately from the main workspace). + // Generally a no-op in vertical mode but likely to happen in horizontal + // mode. + var scrollX = this.workspace_.scrollX; + var scale = this.workspace_.scale; + xyOld.x += scrollX / scale - scrollX; + // If the flyout is on the right side, (0, 0) in the flyout is offset to + // the right of (0, 0) in the main workspace. Add an offset to take that + // into account. + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { + scrollX = targetWorkspace.getMetrics().viewWidth - this.width_; + scale = targetWorkspace.scale; + // Scale the scroll (getSvgXY_ did not do this). + xyOld.x += scrollX / scale - scrollX; + } + + // Take into account that the flyout might have been scrolled vertically + // (separately from the main workspace). + // Generally a no-op in horizontal mode but likely to happen in vertical + // mode. + var scrollY = this.workspace_.scrollY; + scale = this.workspace_.scale; + xyOld.y += scrollY / scale - scrollY; + // If the flyout is on the bottom, (0, 0) in the flyout is offset to be below + // (0, 0) in the main workspace. Add an offset to take that into account. + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + scrollY = targetWorkspace.getMetrics().viewHeight - this.height_; + scale = targetWorkspace.scale; + xyOld.y += scrollY / scale - scrollY; + } + + // Create the new block by cloning the block in the flyout (via XML). + var xml = Blockly.Xml.blockToDom(originBlock); + var block = Blockly.Xml.domToBlock(xml, targetWorkspace); + var svgRootNew = block.getSvgRoot(); + if (!svgRootNew) { + throw 'block is not rendered.'; + } + // Figure out where the new block got placed on the screen, relative to the + // upper left corner of the workspace. This may not be the same as the + // original block because the flyout's origin may not be the same as the + // main workspace's origin. + var xyNew = Blockly.getSvgXY_(svgRootNew, targetWorkspace); + // Scale the scroll (getSvgXY_ did not do this). + xyNew.x += + targetWorkspace.scrollX / targetWorkspace.scale - targetWorkspace.scrollX; + xyNew.y += + targetWorkspace.scrollY / targetWorkspace.scale - targetWorkspace.scrollY; + // If the flyout is collapsible and the workspace can't be scrolled. + if (targetWorkspace.toolbox_ && !targetWorkspace.scrollbar) { + xyNew.x += targetWorkspace.toolbox_.getWidth() / targetWorkspace.scale; + xyNew.y += targetWorkspace.toolbox_.getHeight() / targetWorkspace.scale; + } + + // Move the new block to where the old block is. + block.moveBy(xyOld.x - xyNew.x, xyOld.y - xyNew.y); + return block; +}; + +/** + * Filter the blocks on the flyout to disable the ones that are above the + * capacity limit. + * @private + */ +Blockly.Flyout.prototype.filterForCapacity_ = function() { + var remainingCapacity = this.targetWorkspace_.remainingCapacity(); + var blocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + if (this.permanentlyDisabled_.indexOf(block) == -1) { + var allBlocks = block.getDescendants(); + block.setDisabled(allBlocks.length > remainingCapacity); + } + } +}; + +/** + * Return the deletion rectangle for this flyout. + * @return {goog.math.Rect} Rectangle in which to delete. + */ +Blockly.Flyout.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + + var flyoutRect = this.svgGroup_.getBoundingClientRect(); + // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout + // area are still deleted. Must be larger than the largest screen size, + // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE). + var BIG_NUM = 1000000000; + var x = flyoutRect.left; + var y = flyoutRect.top; + var width = flyoutRect.width; + var height = flyoutRect.height; + + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + return new goog.math.Rect(-BIG_NUM, y - BIG_NUM, BIG_NUM * 2, + BIG_NUM + height); + } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + return new goog.math.Rect(-BIG_NUM, y, BIG_NUM * 2, + BIG_NUM + height); + } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + return new goog.math.Rect(x - BIG_NUM, -BIG_NUM, BIG_NUM + width, + BIG_NUM * 2); + } else { // Right + return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, BIG_NUM * 2); + } +}; + +/** + * Stop binding to the global mouseup and mousemove events. + * @private + */ +Blockly.Flyout.terminateDrag_ = function() { + if (Blockly.Flyout.startFlyout_) { + Blockly.Flyout.startFlyout_.dragMode_ = Blockly.DRAG_NONE; + } + if (Blockly.Flyout.onMouseUpWrapper_) { + Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_); + Blockly.Flyout.onMouseUpWrapper_ = null; + } + if (Blockly.Flyout.onMouseMoveBlockWrapper_) { + Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveBlockWrapper_); + Blockly.Flyout.onMouseMoveBlockWrapper_ = null; + } + if (Blockly.Flyout.onMouseMoveWrapper_) { + Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_); + Blockly.Flyout.onMouseMoveWrapper_ = null; + } + Blockly.Flyout.startDownEvent_ = null; + Blockly.Flyout.startBlock_ = null; + Blockly.Flyout.startFlyout_ = null; +}; + +/** + * Compute height of flyout. Position button under each block. + * For RTL: Lay out the blocks right-aligned. + * @param {!Array} blocks The blocks to reflow. + */ +Blockly.Flyout.prototype.reflowHorizontal = function(blocks) { + this.workspace_.scale = this.targetWorkspace_.scale; + var flyoutHeight = 0; + for (var i = 0, block; block = blocks[i]; i++) { + flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height); + } + flyoutHeight += this.MARGIN * 1.5; + flyoutHeight *= this.workspace_.scale; + flyoutHeight += Blockly.Scrollbar.scrollbarThickness; + if (this.height_ != flyoutHeight) { + for (var i = 0, block; block = blocks[i]; i++) { + var blockHW = block.getHeightWidth(); + if (block.flyoutRect_) { + block.flyoutRect_.setAttribute('width', blockHW.width); + block.flyoutRect_.setAttribute('height', blockHW.height); + // Rectangles behind blocks with output tabs are shifted a bit. + var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; + var blockXY = block.getRelativeToSurfaceXY(); + block.flyoutRect_.setAttribute('y', blockXY.y); + block.flyoutRect_.setAttribute('x', + this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab); + // For hat blocks we want to shift them down by the hat height + // since the y coordinate is the corner, not the top of the hat. + var hatOffset = + block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0; + if (hatOffset) { + block.moveBy(0, hatOffset); + } + block.flyoutRect_.setAttribute('y', blockXY.y); + } + } + // Record the height for .getMetrics_ and .position. + this.height_ = flyoutHeight; + // Call this since it is possible the trash and zoom buttons need + // to move. e.g. on a bottom positioned flyout when zoom is clicked. + this.targetWorkspace_.resize(); + } +}; + +/** + * Compute width of flyout. Position button under each block. + * For RTL: Lay out the blocks right-aligned. + * @param {!Array} blocks The blocks to reflow. + */ +Blockly.Flyout.prototype.reflowVertical = function(blocks) { + this.workspace_.scale = this.targetWorkspace_.scale; + var flyoutWidth = 0; + for (var i = 0, block; block = blocks[i]; i++) { + var width = block.getHeightWidth().width; + if (block.outputConnection) { + width -= Blockly.BlockSvg.TAB_WIDTH; + } + flyoutWidth = Math.max(flyoutWidth, width); + } + for (var i = 0, button; button = this.buttons_[i]; i++) { + flyoutWidth = Math.max(flyoutWidth, button.width); + } + flyoutWidth += this.MARGIN * 1.5 + Blockly.BlockSvg.TAB_WIDTH; + flyoutWidth *= this.workspace_.scale; + flyoutWidth += Blockly.Scrollbar.scrollbarThickness; + if (this.width_ != flyoutWidth) { + for (var i = 0, block; block = blocks[i]; i++) { + var blockHW = block.getHeightWidth(); + if (this.RTL) { + // With the flyoutWidth known, right-align the blocks. + var oldX = block.getRelativeToSurfaceXY().x; + var newX = flyoutWidth / this.workspace_.scale - this.MARGIN; + newX -= Blockly.BlockSvg.TAB_WIDTH; + block.moveBy(newX - oldX, 0); + } + if (block.flyoutRect_) { + block.flyoutRect_.setAttribute('width', blockHW.width); + block.flyoutRect_.setAttribute('height', blockHW.height); + // Blocks with output tabs are shifted a bit. + var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; + var blockXY = block.getRelativeToSurfaceXY(); + block.flyoutRect_.setAttribute('x', + this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab); + // For hat blocks we want to shift them down by the hat height + // since the y coordinate is the corner, not the top of the hat. + var hatOffset = + block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0; + if (hatOffset) { + block.moveBy(0, hatOffset); + } + block.flyoutRect_.setAttribute('y', blockXY.y); + } + } + // Record the width for .getMetrics_ and .position. + this.width_ = flyoutWidth; + // Call this since it is possible the trash and zoom buttons need + // to move. e.g. on a bottom positioned flyout when zoom is clicked. + this.targetWorkspace_.resize(); + } +}; + +/** + * Reflow blocks and their buttons. + */ +Blockly.Flyout.prototype.reflow = function() { + if (this.reflowWrapper_) { + this.workspace_.removeChangeListener(this.reflowWrapper_); + } + var blocks = this.workspace_.getTopBlocks(false); + if (this.horizontalLayout_) { + this.reflowHorizontal(blocks); + } else { + this.reflowVertical(blocks); + } + if (this.reflowWrapper_) { + this.workspace_.addChangeListener(this.reflowWrapper_); + } +}; diff --git a/src/blockly/core/flyout_button.js b/src/blockly/core/flyout_button.js new file mode 100644 index 0000000..75b7a83 --- /dev/null +++ b/src/blockly/core/flyout_button.js @@ -0,0 +1,169 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Class for a button in the flyout. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.FlyoutButton'); + +goog.require('goog.dom'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for a button in the flyout. + * @param {!Blockly.Workspace} workspace The workspace in which to place this + * button. + * @param {!Blockly.Workspace} targetWorkspace The flyout's target workspace. + * @param {string} text The text to display on the button. + * @constructor + */ +Blockly.FlyoutButton = function(workspace, targetWorkspace, text) { + /** + * @type {!Blockly.Workspace} + * @private + */ + this.workspace_ = workspace; + + /** + * @type {!Blockly.Workspace} + * @private + */ + this.targetWorkspace_ = targetWorkspace; + + /** + * @type {string} + * @private + */ + this.text_ = text; + + /** + * @type {goog.math.Coordinate} + * @private + */ + this.position_ = new goog.math.Coordinate(0, 0); +}; + +/** + * The margin around the text in the button. + */ +Blockly.FlyoutButton.MARGIN = 5; + +/** + * The width of the button's rect. + * @type {number} + */ +Blockly.FlyoutButton.prototype.width = 0; + +/** + * The height of the button's rect. + * @type {number} + */ +Blockly.FlyoutButton.prototype.height = 0; + +/** + * Create the button elements. + * @return {!Element} The button's SVG group. + */ +Blockly.FlyoutButton.prototype.createDom = function() { + this.svgGroup_ = Blockly.createSvgElement('g', + {'class': 'blocklyFlyoutButton'}, this.workspace_.getCanvas()); + + // Rect with rounded corners. + var rect = Blockly.createSvgElement('rect', + {'rx': 4, 'ry': 4, + 'height': 0, 'width': 0}, + this.svgGroup_); + + var svgText = Blockly.createSvgElement('text', + {'class': 'blocklyText', 'x': 0, 'y': 0, + 'text-anchor': 'middle'}, this.svgGroup_); + svgText.textContent = this.text_; + + this.width = svgText.getComputedTextLength() + + 2 * Blockly.FlyoutButton.MARGIN; + this.height = 20; // Can't compute it :( + + rect.setAttribute('width', this.width); + rect.setAttribute('height', this.height); + + svgText.setAttribute('x', this.width / 2); + svgText.setAttribute('y', this.height - Blockly.FlyoutButton.MARGIN); + + this.updateTransform_(); + return this.svgGroup_; +}; + +/** + * Correctly position the flyout button and make it visible. + */ +Blockly.FlyoutButton.prototype.show = function() { + this.updateTransform_(); + this.svgGroup_.setAttribute('display', 'block'); +}; + +/** + * Update svg attributes to match internal state. + */ +Blockly.FlyoutButton.prototype.updateTransform_ = function() { + this.svgGroup_.setAttribute('transform', 'translate(' + this.position_.x + + ',' + this.position_.y + ')'); +}; + +/** + * Move the button to the given x, y coordinates. + * @param {number} x The new x coordinate. + * @param {number} y The new y coordinate. + */ +Blockly.FlyoutButton.prototype.moveTo = function(x, y) { + this.position_.x = x; + this.position_.y = y; + this.updateTransform_(); +}; + +/** + * Dispose of this button. + */ +Blockly.FlyoutButton.prototype.dispose = function() { + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.workspace_ = null; + this.targetWorkspace_ = null; +}; + +/** + * Do something when the button is clicked. + * @param {!Event} e Mouse up event. + */ +Blockly.FlyoutButton.prototype.onMouseUp = function(e) { + // Don't scroll the page. + e.preventDefault(); + // Don't propagate mousewheel event (zooming). + e.stopPropagation(); + // Stop binding to mouseup and mousemove events--flyout mouseup would normally + // do this, but we're skipping that. + Blockly.Flyout.terminateDrag_(); + Blockly.Variables.createVariable(this.targetWorkspace_); +}; diff --git a/src/blockly/core/generator.js b/src/blockly/core/generator.js new file mode 100644 index 0000000..fecc355 --- /dev/null +++ b/src/blockly/core/generator.js @@ -0,0 +1,369 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Utility functions for generating executable code from + * Blockly code. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Generator'); + +goog.require('Blockly.Block'); +goog.require('goog.asserts'); + + +/** + * Class for a code generator that translates the blocks into a language. + * @param {string} name Language name of this generator. + * @constructor + */ +Blockly.Generator = function(name) { + this.name_ = name; + this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ = + new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g'); +}; + +/** + * Category to separate generated function names from variables and procedures. + */ +Blockly.Generator.NAME_TYPE = 'generated_function'; + +/** + * Arbitrary code to inject into locations that risk causing infinite loops. + * Any instances of '%1' will be replaced by the block ID that failed. + * E.g. ' checkTimeout(%1);\n' + * @type {?string} + */ +Blockly.Generator.prototype.INFINITE_LOOP_TRAP = null; + +/** + * Arbitrary code to inject before every statement. + * Any instances of '%1' will be replaced by the block ID of the statement. + * E.g. 'highlight(%1);\n' + * @type {?string} + */ +Blockly.Generator.prototype.STATEMENT_PREFIX = null; + +/** + * The method of indenting. Defaults to two spaces, but language generators + * may override this to increase indent or change to tabs. + * @type {string} + */ +Blockly.Generator.prototype.INDENT = ' '; + +/** + * Maximum length for a comment before wrapping. Does not account for + * indenting level. + * @type {number} + */ +Blockly.Generator.prototype.COMMENT_WRAP = 60; + +/** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array.>} + */ +Blockly.Generator.prototype.ORDER_OVERRIDES = []; + +/** + * Generate code for all blocks in the workspace to the specified language. + * @param {Blockly.Workspace} workspace Workspace to generate code from. + * @return {string} Generated code. + */ +Blockly.Generator.prototype.workspaceToCode = function(workspace) { + if (!workspace) { + // Backwards compatibility from before there could be multiple workspaces. + console.warn('No workspace specified in workspaceToCode call. Guessing.'); + workspace = Blockly.getMainWorkspace(); + } + var code = []; + this.init(workspace); + var blocks = workspace.getTopBlocks(true); + for (var x = 0, block; block = blocks[x]; x++) { + var line = this.blockToCode(block); + if (goog.isArray(line)) { + // Value blocks return tuples of code and operator order. + // Top-level blocks don't care about operator order. + line = line[0]; + } + if (line) { + if (block.outputConnection && this.scrubNakedValue) { + // This block is a naked value. Ask the language's code generator if + // it wants to append a semicolon, or something. + line = this.scrubNakedValue(line); + } + code.push(line); + } + } + code = code.join('\n'); // Blank line between each section. + code = this.finish(code); + // Final scrubbing of whitespace. + code = code.replace(/^\s+\n/, ''); + code = code.replace(/\n\s+$/, '\n'); + code = code.replace(/[ \t]+\n/g, '\n'); + return code; +}; + +// The following are some helpful functions which can be used by multiple +// languages. + +/** + * Prepend a common prefix onto each line of code. + * @param {string} text The lines of code. + * @param {string} prefix The common prefix. + * @return {string} The prefixed lines of code. + */ +Blockly.Generator.prototype.prefixLines = function(text, prefix) { + return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix); +}; + +/** + * Recursively spider a tree of blocks, returning all their comments. + * @param {!Blockly.Block} block The block from which to start spidering. + * @return {string} Concatenated list of comments. + */ +Blockly.Generator.prototype.allNestedComments = function(block) { + var comments = []; + var blocks = block.getDescendants(); + for (var i = 0; i < blocks.length; i++) { + var comment = blocks[i].getCommentText(); + if (comment) { + comments.push(comment); + } + } + // Append an empty string to create a trailing line break when joined. + if (comments.length) { + comments.push(''); + } + return comments.join('\n'); +}; + +/** + * Generate code for the specified block (and attached blocks). + * @param {Blockly.Block} block The block to generate code for. + * @return {string|!Array} For statement blocks, the generated code. + * For value blocks, an array containing the generated code and an + * operator order value. Returns '' if block is null. + */ +Blockly.Generator.prototype.blockToCode = function(block) { + if (!block) { + return ''; + } + if (block.disabled) { + // Skip past this block if it is disabled. + return this.blockToCode(block.getNextBlock()); + } + + var func = this[block.type]; + goog.asserts.assertFunction(func, + 'Language "%s" does not know how to generate code for block type "%s".', + this.name_, block.type); + // First argument to func.call is the value of 'this' in the generator. + // Prior to 24 September 2013 'this' was the only way to access the block. + // The current prefered method of accessing the block is through the second + // argument to func.call, which becomes the first parameter to the generator. + var code = func.call(block, block); + if (goog.isArray(code)) { + // Value blocks return tuples of code and operator order. + goog.asserts.assert(block.outputConnection, + 'Expecting string from statement block "%s".', block.type); + return [this.scrub_(block, code[0]), code[1]]; + } else if (goog.isString(code)) { + if (this.STATEMENT_PREFIX) { + code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + block.id + '\'') + + code; + } + return this.scrub_(block, code); + } else if (code === null) { + // Block has handled code generation itself. + return ''; + } else { + goog.asserts.fail('Invalid code generated: %s', code); + } +}; + +/** + * Generate code representing the specified value input. + * @param {!Blockly.Block} block The block containing the input. + * @param {string} name The name of the input. + * @param {number} outerOrder The maximum binding strength (minimum order value) + * of any operators adjacent to "block". + * @return {string} Generated code or '' if no blocks are connected or the + * specified input does not exist. + */ +Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) { + if (isNaN(outerOrder)) { + goog.asserts.fail('Expecting valid order from block "%s".', block.type); + } + var targetBlock = block.getInputTargetBlock(name); + if (!targetBlock) { + return ''; + } + var tuple = this.blockToCode(targetBlock); + if (tuple === '') { + // Disabled block. + return ''; + } + // Value blocks must return code and order of operations info. + // Statement blocks must only return code. + goog.asserts.assertArray(tuple, 'Expecting tuple from value block "%s".', + targetBlock.type); + var code = tuple[0]; + var innerOrder = tuple[1]; + if (isNaN(innerOrder)) { + goog.asserts.fail('Expecting valid order from value block "%s".', + targetBlock.type); + } + if (!code) { + return ''; + } + + // Add parentheses if needed. + var parensNeeded = false; + var outerOrderClass = Math.floor(outerOrder); + var innerOrderClass = Math.floor(innerOrder); + if (outerOrderClass <= innerOrderClass) { + if (outerOrderClass == innerOrderClass && + (outerOrderClass == 0 || outerOrderClass == 99)) { + // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs. + // 0 is the atomic order, 99 is the none order. No parentheses needed. + // In all known languages multiple such code blocks are not order + // sensitive. In fact in Python ('a' 'b') 'c' would fail. + } else { + // The operators outside this code are stonger than the operators + // inside this code. To prevent the code from being pulled apart, + // wrap the code in parentheses. + parensNeeded = true; + // Check for special exceptions. + for (var i = 0; i < this.ORDER_OVERRIDES.length; i++) { + if (this.ORDER_OVERRIDES[i][0] == outerOrder && + this.ORDER_OVERRIDES[i][1] == innerOrder) { + parensNeeded = false; + break; + } + } + } + } + if (parensNeeded) { + // Technically, this should be handled on a language-by-language basis. + // However all known (sane) languages use parentheses for grouping. + code = '(' + code + ')'; + } + return code; +}; + +/** + * Generate code representing the statement. Indent the code. + * @param {!Blockly.Block} block The block containing the input. + * @param {string} name The name of the input. + * @return {string} Generated code or '' if no blocks are connected. + */ +Blockly.Generator.prototype.statementToCode = function(block, name) { + var targetBlock = block.getInputTargetBlock(name); + var code = this.blockToCode(targetBlock); + // Value blocks must return code and order of operations info. + // Statement blocks must only return code. + goog.asserts.assertString(code, 'Expecting code from statement block "%s".', + targetBlock && targetBlock.type); + if (code) { + code = this.prefixLines(/** @type {string} */ (code), this.INDENT); + } + return code; +}; + +/** + * Add an infinite loop trap to the contents of a loop. + * If loop is empty, add a statment prefix for the loop block. + * @param {string} branch Code for loop contents. + * @param {string} id ID of enclosing block. + * @return {string} Loop contents, with infinite loop trap added. + */ +Blockly.Generator.prototype.addLoopTrap = function(branch, id) { + if (this.INFINITE_LOOP_TRAP) { + branch = this.INFINITE_LOOP_TRAP.replace(/%1/g, '\'' + id + '\'') + branch; + } + if (this.STATEMENT_PREFIX) { + branch += this.prefixLines(this.STATEMENT_PREFIX.replace(/%1/g, + '\'' + id + '\''), this.INDENT); + } + return branch; +}; + +/** + * Comma-separated list of reserved words. + * @type {string} + * @private + */ +Blockly.Generator.prototype.RESERVED_WORDS_ = ''; + +/** + * Add one or more words to the list of reserved words for this language. + * @param {string} words Comma-separated list of words to add to the list. + * No spaces. Duplicates are ok. + */ +Blockly.Generator.prototype.addReservedWords = function(words) { + this.RESERVED_WORDS_ += words + ','; +}; + +/** + * This is used as a placeholder in functions defined using + * Blockly.Generator.provideFunction_. It must not be legal code that could + * legitimately appear in a function definition (or comment), and it must + * not confuse the regular expression parser. + * @type {string} + * @private + */ +Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}'; + +/** + * Define a function to be included in the generated code. + * The first time this is called with a given desiredName, the code is + * saved and an actual name is generated. Subsequent calls with the + * same desiredName have no effect but have the same return value. + * + * It is up to the caller to make sure the same desiredName is not + * used for different code values. + * + * The code gets output when Blockly.Generator.finish() is called. + * + * @param {string} desiredName The desired name of the function (e.g., isPrime). + * @param {!Array.} code A list of statements. Use ' ' for indents. + * @return {string} The actual name of the new function. This may differ + * from desiredName if the former has already been taken by the user. + * @private + */ +Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) { + if (!this.definitions_[desiredName]) { + var functionName = this.variableDB_.getDistinctName(desiredName, + Blockly.Procedures.NAME_TYPE); + this.functionNames_[desiredName] = functionName; + var codeText = code.join('\n').replace( + this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName); + // Change all ' ' indents into the desired indent. + var oldCodeText; + while (oldCodeText != codeText) { + oldCodeText = codeText; + codeText = codeText.replace(/^(( )*) /gm, '$1' + this.INDENT); + } + this.definitions_[desiredName] = codeText; + } + return this.functionNames_[desiredName]; +}; diff --git a/src/blockly/core/icon.js b/src/blockly/core/icon.js new file mode 100644 index 0000000..b10e181 --- /dev/null +++ b/src/blockly/core/icon.js @@ -0,0 +1,203 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing an icon on a block. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Icon'); + +goog.require('goog.dom'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for an icon. + * @param {Blockly.Block} block The block associated with this icon. + * @constructor + */ +Blockly.Icon = function(block) { + this.block_ = block; +}; + +/** + * Does this icon get hidden when the block is collapsed. + */ +Blockly.Icon.prototype.collapseHidden = true; + +/** + * Height and width of icons. + */ +Blockly.Icon.prototype.SIZE = 17; + +/** + * Bubble UI (if visible). + * @type {Blockly.Bubble} + * @private + */ +Blockly.Icon.prototype.bubble_ = null; + +/** + * Absolute coordinate of icon's center. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.Icon.prototype.iconXY_ = null; + +/** + * Create the icon on the block. + */ +Blockly.Icon.prototype.createIcon = function() { + if (this.iconGroup_) { + // Icon already exists. + return; + } + /* Here's the markup that will be generated: + + ... + + */ + this.iconGroup_ = Blockly.createSvgElement('g', + {'class': 'blocklyIconGroup'}, null); + if (this.block_.isInFlyout) { + Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } + this.drawIcon_(this.iconGroup_); + + this.block_.getSvgRoot().appendChild(this.iconGroup_); + Blockly.bindEvent_(this.iconGroup_, 'mouseup', this, this.iconClick_); + this.updateEditable(); +}; + +/** + * Dispose of this icon. + */ +Blockly.Icon.prototype.dispose = function() { + // Dispose of and unlink the icon. + goog.dom.removeNode(this.iconGroup_); + this.iconGroup_ = null; + // Dispose of and unlink the bubble. + this.setVisible(false); + this.block_ = null; +}; + +/** + * Add or remove the UI indicating if this icon may be clicked or not. + */ +Blockly.Icon.prototype.updateEditable = function() { +}; + +/** + * Is the associated bubble visible? + * @return {boolean} True if the bubble is visible. + */ +Blockly.Icon.prototype.isVisible = function() { + return !!this.bubble_; +}; + +/** + * Clicking on the icon toggles if the bubble is visible. + * @param {!Event} e Mouse click event. + * @private + */ +Blockly.Icon.prototype.iconClick_ = function(e) { + if (this.block_.workspace.isDragging()) { + // Drag operation is concluding. Don't open the editor. + return; + } + if (!this.block_.isInFlyout && !Blockly.isRightButton(e)) { + this.setVisible(!this.isVisible()); + } +}; + +/** + * Change the colour of the associated bubble to match its block. + */ +Blockly.Icon.prototype.updateColour = function() { + if (this.isVisible()) { + this.bubble_.setColour(this.block_.getColour()); + } +}; + +/** + * Render the icon. + * @param {number} cursorX Horizontal offset at which to position the icon. + * @return {number} Horizontal offset for next item to draw. + */ +Blockly.Icon.prototype.renderIcon = function(cursorX) { + if (this.collapseHidden && this.block_.isCollapsed()) { + this.iconGroup_.setAttribute('display', 'none'); + return cursorX; + } + this.iconGroup_.setAttribute('display', 'block'); + + var TOP_MARGIN = 5; + var width = this.SIZE; + if (this.block_.RTL) { + cursorX -= width; + } + this.iconGroup_.setAttribute('transform', + 'translate(' + cursorX + ',' + TOP_MARGIN + ')'); + this.computeIconLocation(); + if (this.block_.RTL) { + cursorX -= Blockly.BlockSvg.SEP_SPACE_X; + } else { + cursorX += width + Blockly.BlockSvg.SEP_SPACE_X; + } + return cursorX; +}; + +/** + * Notification that the icon has moved. Update the arrow accordingly. + * @param {!goog.math.Coordinate} xy Absolute location. + */ +Blockly.Icon.prototype.setIconLocation = function(xy) { + this.iconXY_ = xy; + if (this.isVisible()) { + this.bubble_.setAnchorLocation(xy); + } +}; + +/** + * Notification that the icon has moved, but we don't really know where. + * Recompute the icon's location from scratch. + */ +Blockly.Icon.prototype.computeIconLocation = function() { + // Find coordinates for the centre of the icon and update the arrow. + var blockXY = this.block_.getRelativeToSurfaceXY(); + var iconXY = Blockly.getRelativeXY_(this.iconGroup_); + var newXY = new goog.math.Coordinate( + blockXY.x + iconXY.x + this.SIZE / 2, + blockXY.y + iconXY.y + this.SIZE / 2); + if (!goog.math.Coordinate.equals(this.getIconLocation(), newXY)) { + this.setIconLocation(newXY); + } +}; + +/** + * Returns the center of the block's icon relative to the surface. + * @return {!goog.math.Coordinate} Object with x and y properties. + */ +Blockly.Icon.prototype.getIconLocation = function() { + return this.iconXY_; +}; diff --git a/src/blockly/core/inject.js b/src/blockly/core/inject.js new file mode 100644 index 0000000..57e3e51 --- /dev/null +++ b/src/blockly/core/inject.js @@ -0,0 +1,378 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Functions for injecting Blockly into a web page. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.inject'); + +goog.require('Blockly.Css'); +goog.require('Blockly.Options'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.dom'); +goog.require('goog.ui.Component'); +goog.require('goog.userAgent'); + + +/** + * Inject a Blockly editor into the specified container element (usually a div). + * @param {!Element|string} container Containing element, or its ID, + * or a CSS selector. + * @param {Object=} opt_options Optional dictionary of options. + * @return {!Blockly.Workspace} Newly created main workspace. + */ +Blockly.inject = function(container, opt_options) { + if (goog.isString(container)) { + container = document.getElementById(container) || + document.querySelector(container); + } + // Verify that the container is in document. + if (!goog.dom.contains(document, container)) { + throw 'Error: container is not in current document.'; + } + var options = new Blockly.Options(opt_options || {}); + var subContainer = goog.dom.createDom('div', 'injectionDiv'); + container.appendChild(subContainer); + var svg = Blockly.createDom_(subContainer, options); + var workspace = Blockly.createMainWorkspace_(svg, options); + Blockly.init_(workspace); + workspace.markFocused(); + Blockly.bindEvent_(svg, 'focus', workspace, workspace.markFocused); + Blockly.svgResize(workspace); + return workspace; +}; + +/** + * Create the SVG image. + * @param {!Element} container Containing element. + * @param {!Blockly.Options} options Dictionary of options. + * @return {!Element} Newly created SVG image. + * @private + */ +Blockly.createDom_ = function(container, options) { + // Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying + // out content in RTL mode. Therefore Blockly forces the use of LTR, + // then manually positions content in RTL as needed. + container.setAttribute('dir', 'LTR'); + // Closure can be trusted to create HTML widgets with the proper direction. + goog.ui.Component.setDefaultRightToLeft(options.RTL); + + // Load CSS. + Blockly.Css.inject(options.hasCss, options.pathToMedia); + + // Build the SVG DOM. + /* + + ... + + */ + var svg = Blockly.createSvgElement('svg', { + 'xmlns': 'http://www.w3.org/2000/svg', + 'xmlns:html': 'http://www.w3.org/1999/xhtml', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'version': '1.1', + 'class': 'blocklySvg' + }, container); + /* + + ... filters go here ... + + */ + var defs = Blockly.createSvgElement('defs', {}, svg); + var rnd = String(Math.random()).substring(2); + /* + + + + + + + + + */ + var embossFilter = Blockly.createSvgElement('filter', + {'id': 'blocklyEmbossFilter' + rnd}, defs); + Blockly.createSvgElement('feGaussianBlur', + {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter); + var feSpecularLighting = Blockly.createSvgElement('feSpecularLighting', + {'in': 'blur', 'surfaceScale': 1, 'specularConstant': 0.5, + 'specularExponent': 10, 'lighting-color': 'white', 'result': 'specOut'}, + embossFilter); + Blockly.createSvgElement('fePointLight', + {'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting); + Blockly.createSvgElement('feComposite', + {'in': 'specOut', 'in2': 'SourceAlpha', 'operator': 'in', + 'result': 'specOut'}, embossFilter); + Blockly.createSvgElement('feComposite', + {'in': 'SourceGraphic', 'in2': 'specOut', 'operator': 'arithmetic', + 'k1': 0, 'k2': 1, 'k3': 1, 'k4': 0}, embossFilter); + options.embossFilterId = embossFilter.id; + /* + + + + + */ + var disabledPattern = Blockly.createSvgElement('pattern', + {'id': 'blocklyDisabledPattern' + rnd, + 'patternUnits': 'userSpaceOnUse', + 'width': 10, 'height': 10}, defs); + Blockly.createSvgElement('rect', + {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); + Blockly.createSvgElement('path', + {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern); + options.disabledPatternId = disabledPattern.id; + /* + + + + + */ + var gridPattern = Blockly.createSvgElement('pattern', + {'id': 'blocklyGridPattern' + rnd, + 'patternUnits': 'userSpaceOnUse'}, defs); + if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) { + Blockly.createSvgElement('line', + {'stroke': options.gridOptions['colour']}, + gridPattern); + if (options.gridOptions['length'] > 1) { + Blockly.createSvgElement('line', + {'stroke': options.gridOptions['colour']}, + gridPattern); + } + // x1, y1, x1, x2 properties will be set later in updateGridPattern_. + } + options.gridPattern = gridPattern; + return svg; +}; + +/** + * Create a main workspace and add it to the SVG. + * @param {!Element} svg SVG element with pattern defined. + * @param {!Blockly.Options} options Dictionary of options. + * @return {!Blockly.Workspace} Newly created main workspace. + * @private + */ +Blockly.createMainWorkspace_ = function(svg, options) { + options.parentWorkspace = null; + var mainWorkspace = new Blockly.WorkspaceSvg(options); + mainWorkspace.scale = options.zoomOptions.startScale; + svg.appendChild(mainWorkspace.createDom('blocklyMainBackground')); + // A null translation will also apply the correct initial scale. + mainWorkspace.translate(0, 0); + mainWorkspace.markFocused(); + + if (!options.readOnly && !options.hasScrollbars) { + var workspaceChanged = function() { + if (Blockly.dragMode_ == Blockly.DRAG_NONE) { + var metrics = mainWorkspace.getMetrics(); + var edgeLeft = metrics.viewLeft + metrics.absoluteLeft; + var edgeTop = metrics.viewTop + metrics.absoluteTop; + if (metrics.contentTop < edgeTop || + metrics.contentTop + metrics.contentHeight > + metrics.viewHeight + edgeTop || + metrics.contentLeft < + (options.RTL ? metrics.viewLeft : edgeLeft) || + metrics.contentLeft + metrics.contentWidth > (options.RTL ? + metrics.viewWidth : metrics.viewWidth + edgeLeft)) { + // One or more blocks may be out of bounds. Bump them back in. + var MARGIN = 25; + var blocks = mainWorkspace.getTopBlocks(false); + for (var b = 0, block; block = blocks[b]; b++) { + var blockXY = block.getRelativeToSurfaceXY(); + var blockHW = block.getHeightWidth(); + // Bump any block that's above the top back inside. + var overflowTop = edgeTop + MARGIN - blockHW.height - blockXY.y; + if (overflowTop > 0) { + block.moveBy(0, overflowTop); + } + // Bump any block that's below the bottom back inside. + var overflowBottom = + edgeTop + metrics.viewHeight - MARGIN - blockXY.y; + if (overflowBottom < 0) { + block.moveBy(0, overflowBottom); + } + // Bump any block that's off the left back inside. + var overflowLeft = MARGIN + edgeLeft - + blockXY.x - (options.RTL ? 0 : blockHW.width); + if (overflowLeft > 0) { + block.moveBy(overflowLeft, 0); + } + // Bump any block that's off the right back inside. + var overflowRight = edgeLeft + metrics.viewWidth - MARGIN - + blockXY.x + (options.RTL ? blockHW.width : 0); + if (overflowRight < 0) { + block.moveBy(overflowRight, 0); + } + } + } + } + }; + mainWorkspace.addChangeListener(workspaceChanged); + } + // The SVG is now fully assembled. + Blockly.svgResize(mainWorkspace); + Blockly.WidgetDiv.createDom(); + Blockly.Tooltip.createDom(); + return mainWorkspace; +}; + +/** + * Initialize Blockly with various handlers. + * @param {!Blockly.Workspace} mainWorkspace Newly created main workspace. + * @private + */ +Blockly.init_ = function(mainWorkspace) { + var options = mainWorkspace.options; + var svg = mainWorkspace.getParentSvg(); + + // Supress the browser's context menu. + Blockly.bindEvent_(svg, 'contextmenu', null, + function(e) { + if (!Blockly.isTargetInput_(e)) { + e.preventDefault(); + } + }); + + var workspaceResizeHandler = Blockly.bindEvent_(window, 'resize', null, + function() { + Blockly.hideChaff(true); + Blockly.svgResize(mainWorkspace); + }); + mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler); + + Blockly.inject.bindDocumentEvents_(); + + if (options.languageTree) { + if (mainWorkspace.toolbox_) { + mainWorkspace.toolbox_.init(mainWorkspace); + } else if (mainWorkspace.flyout_) { + // Build a fixed flyout with the root blocks. + mainWorkspace.flyout_.init(mainWorkspace); + mainWorkspace.flyout_.show(options.languageTree.childNodes); + mainWorkspace.flyout_.scrollToStart(); + // Translate the workspace sideways to avoid the fixed flyout. + mainWorkspace.scrollX = mainWorkspace.flyout_.width_; + if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + mainWorkspace.scrollX *= -1; + } + mainWorkspace.translate(mainWorkspace.scrollX, 0); + } + } + + if (options.hasScrollbars) { + mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace); + mainWorkspace.scrollbar.resize(); + } + + // Load the sounds. + if (options.hasSounds) { + Blockly.inject.loadSounds_(options.pathToMedia, mainWorkspace); + } +}; + +/** + * Bind document events, but only once. Destroying and reinjecting Blockly + * should not bind again. + * Bind events for scrolling the workspace. + * Most of these events should be bound to the SVG's surface. + * However, 'mouseup' has to be on the whole document so that a block dragged + * out of bounds and released will know that it has been released. + * Also, 'keydown' has to be on the whole document since the browser doesn't + * understand a concept of focus on the SVG image. + * @private + */ +Blockly.inject.bindDocumentEvents_ = function() { + if (!Blockly.documentEventsBound_) { + Blockly.bindEvent_(document, 'keydown', null, Blockly.onKeyDown_); + Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_); + Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_); + // Don't use bindEvent_ for document's mouseup since that would create a + // corresponding touch handler that would squeltch the ability to interact + // with non-Blockly elements. + document.addEventListener('mouseup', Blockly.onMouseUp_, false); + // Some iPad versions don't fire resize after portrait to landscape change. + if (goog.userAgent.IPAD) { + Blockly.bindEvent_(window, 'orientationchange', document, function() { + // TODO(#397): Fix for multiple blockly workspaces. + Blockly.svgResize(Blockly.getMainWorkspace()); + }); + } + } + Blockly.documentEventsBound_ = true; +}; + +/** + * Load sounds for the given workspace. + * @param {string} pathToMedia The path to the media directory. + * @param {!Blockly.Workspace} workspace The workspace to load sounds for. + * @private + */ +Blockly.inject.loadSounds_ = function(pathToMedia, workspace) { + workspace.loadAudio_( + [pathToMedia + 'click.mp3', + pathToMedia + 'click.wav', + pathToMedia + 'click.ogg'], 'click'); + workspace.loadAudio_( + [pathToMedia + 'disconnect.wav', + pathToMedia + 'disconnect.mp3', + pathToMedia + 'disconnect.ogg'], 'disconnect'); + workspace.loadAudio_( + [pathToMedia + 'delete.mp3', + pathToMedia + 'delete.ogg', + pathToMedia + 'delete.wav'], 'delete'); + + // Bind temporary hooks that preload the sounds. + var soundBinds = []; + var unbindSounds = function() { + while (soundBinds.length) { + Blockly.unbindEvent_(soundBinds.pop()); + } + workspace.preloadAudio_(); + }; + // Android ignores any sound not loaded as a result of a user action. + soundBinds.push( + Blockly.bindEvent_(document, 'mousemove', null, unbindSounds)); + soundBinds.push( + Blockly.bindEvent_(document, 'touchstart', null, unbindSounds)); +}; + +/** + * Modify the block tree on the existing toolbox. + * @param {Node|string} tree DOM tree of blocks, or text representation of same. + */ +Blockly.updateToolbox = function(tree) { + console.warn('Deprecated call to Blockly.updateToolbox, ' + + 'use workspace.updateToolbox instead.'); + Blockly.getMainWorkspace().updateToolbox(tree); +}; diff --git a/src/blockly/core/input.js b/src/blockly/core/input.js new file mode 100644 index 0000000..4b4eb1d --- /dev/null +++ b/src/blockly/core/input.js @@ -0,0 +1,241 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing an input (value, statement, or dummy). + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Input'); + +goog.require('Blockly.Connection'); +goog.require('Blockly.FieldLabel'); +goog.require('goog.asserts'); + + +/** + * Class for an input with an optional field. + * @param {number} type The type of the input. + * @param {string} name Language-neutral identifier which may used to find this + * input again. + * @param {!Blockly.Block} block The block containing this input. + * @param {Blockly.Connection} connection Optional connection for this input. + * @constructor + */ +Blockly.Input = function(type, name, block, connection) { + /** @type {number} */ + this.type = type; + /** @type {string} */ + this.name = name; + /** + * @type {!Blockly.Block} + * @private + */ + this.sourceBlock_ = block; + /** @type {Blockly.Connection} */ + this.connection = connection; + /** @type {!Array.} */ + this.fieldRow = []; +}; + +/** + * Alignment of input's fields (left, right or centre). + * @type {number} + */ +Blockly.Input.prototype.align = Blockly.ALIGN_LEFT; + +/** + * Is the input visible? + * @type {boolean} + * @private + */ +Blockly.Input.prototype.visible_ = true; + +/** + * Add an item to the end of the input's field row. + * @param {string|!Blockly.Field} field Something to add as a field. + * @param {string=} opt_name Language-neutral identifier which may used to find + * this field again. Should be unique to the host block. + * @return {!Blockly.Input} The input being append to (to allow chaining). + */ +Blockly.Input.prototype.appendField = function(field, opt_name) { + // Empty string, Null or undefined generates no field, unless field is named. + if (!field && !opt_name) { + return this; + } + // Generate a FieldLabel when given a plain text field. + if (goog.isString(field)) { + field = new Blockly.FieldLabel(/** @type {string} */ (field)); + } + field.setSourceBlock(this.sourceBlock_); + if (this.sourceBlock_.rendered) { + field.init(); + } + field.name = opt_name; + + if (field.prefixField) { + // Add any prefix. + this.appendField(field.prefixField); + } + // Add the field to the field row. + this.fieldRow.push(field); + if (field.suffixField) { + // Add any suffix. + this.appendField(field.suffixField); + } + + if (this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + // Adding a field will cause the block to change shape. + this.sourceBlock_.bumpNeighbours_(); + } + return this; +}; + +/** + * Add an item to the end of the input's field row. + * @param {*} field Something to add as a field. + * @param {string=} opt_name Language-neutral identifier which may used to find + * this field again. Should be unique to the host block. + * @return {!Blockly.Input} The input being append to (to allow chaining). + * @deprecated December 2013 + */ +Blockly.Input.prototype.appendTitle = function(field, opt_name) { + console.warn('Deprecated call to appendTitle, use appendField instead.'); + return this.appendField(field, opt_name); +}; + +/** + * Remove a field from this input. + * @param {string} name The name of the field. + * @throws {goog.asserts.AssertionError} if the field is not present. + */ +Blockly.Input.prototype.removeField = function(name) { + for (var i = 0, field; field = this.fieldRow[i]; i++) { + if (field.name === name) { + field.dispose(); + this.fieldRow.splice(i, 1); + if (this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + // Removing a field will cause the block to change shape. + this.sourceBlock_.bumpNeighbours_(); + } + return; + } + } + goog.asserts.fail('Field "%s" not found.', name); +}; + +/** + * Gets whether this input is visible or not. + * @return {boolean} True if visible. + */ +Blockly.Input.prototype.isVisible = function() { + return this.visible_; +}; + +/** + * Sets whether this input is visible or not. + * Used to collapse/uncollapse a block. + * @param {boolean} visible True if visible. + * @return {!Array.} List of blocks to render. + */ +Blockly.Input.prototype.setVisible = function(visible) { + var renderList = []; + if (this.visible_ == visible) { + return renderList; + } + this.visible_ = visible; + + var display = visible ? 'block' : 'none'; + for (var y = 0, field; field = this.fieldRow[y]; y++) { + field.setVisible(visible); + } + if (this.connection) { + // Has a connection. + if (visible) { + renderList = this.connection.unhideAll(); + } else { + this.connection.hideAll(); + } + var child = this.connection.targetBlock(); + if (child) { + child.getSvgRoot().style.display = display; + if (!visible) { + child.rendered = false; + } + } + } + return renderList; +}; + +/** + * Change a connection's compatibility. + * @param {string|Array.|null} check Compatible value type or + * list of value types. Null if all types are compatible. + * @return {!Blockly.Input} The input being modified (to allow chaining). + */ +Blockly.Input.prototype.setCheck = function(check) { + if (!this.connection) { + throw 'This input does not have a connection.'; + } + this.connection.setCheck(check); + return this; +}; + +/** + * Change the alignment of the connection's field(s). + * @param {number} align One of Blockly.ALIGN_LEFT, ALIGN_CENTRE, ALIGN_RIGHT. + * In RTL mode directions are reversed, and ALIGN_RIGHT aligns to the left. + * @return {!Blockly.Input} The input being modified (to allow chaining). + */ +Blockly.Input.prototype.setAlign = function(align) { + this.align = align; + if (this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + } + return this; +}; + +/** + * Initialize the fields on this input. + */ +Blockly.Input.prototype.init = function() { + if (!this.sourceBlock_.workspace.rendered) { + return; // Headless blocks don't need fields initialized. + } + for (var i = 0; i < this.fieldRow.length; i++) { + this.fieldRow[i].init(); + } +}; + +/** + * Sever all links to this input. + */ +Blockly.Input.prototype.dispose = function() { + for (var i = 0, field; field = this.fieldRow[i]; i++) { + field.dispose(); + } + if (this.connection) { + this.connection.dispose(); + } + this.sourceBlock_ = null; +}; diff --git a/src/blockly/core/msg.js b/src/blockly/core/msg.js new file mode 100644 index 0000000..4ebcad1 --- /dev/null +++ b/src/blockly/core/msg.js @@ -0,0 +1,62 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Empty name space for the Message singleton. + * @author scr@google.com (Sheridan Rawlins) + */ +'use strict'; + +/** + * Name space for the Msg singleton. + * Msg gets populated in the message files. + */ +goog.provide('Blockly.Msg'); + + +/** + * Back up original getMsg function. + * @type {!Function} + */ +goog.getMsgOrig = goog.getMsg; + +/** + * Gets a localized message. + * Overrides the default Closure function to check for a Blockly.Msg first. + * Used infrequently, only known case is TODAY button in date picker. + * @param {string} str Translatable string, places holders in the form {$foo}. + * @param {Object=} opt_values Maps place holder name to value. + * @return {string} message with placeholders filled. + * @suppress {duplicate} + */ +goog.getMsg = function(str, opt_values) { + var key = goog.getMsg.blocklyMsgMap[str]; + if (key) { + str = Blockly.Msg[key]; + } + return goog.getMsgOrig(str, opt_values); +}; + +/** + * Mapping of Closure messages to Blockly.Msg names. + */ +goog.getMsg.blocklyMsgMap = { + 'Today': 'TODAY' +}; diff --git a/src/blockly/core/mutator.js b/src/blockly/core/mutator.js new file mode 100644 index 0000000..19a0fe8 --- /dev/null +++ b/src/blockly/core/mutator.js @@ -0,0 +1,389 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing a mutator dialog. A mutator allows the + * user to change the shape of a block using a nested blocks editor. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Mutator'); + +goog.require('Blockly.Bubble'); +goog.require('Blockly.Icon'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.Timer'); +goog.require('goog.dom'); + + +/** + * Class for a mutator dialog. + * @param {!Array.} quarkNames List of names of sub-blocks for flyout. + * @extends {Blockly.Icon} + * @constructor + */ +Blockly.Mutator = function(quarkNames) { + Blockly.Mutator.superClass_.constructor.call(this, null); + this.quarkNames_ = quarkNames; +}; +goog.inherits(Blockly.Mutator, Blockly.Icon); + +/** + * Width of workspace. + * @private + */ +Blockly.Mutator.prototype.workspaceWidth_ = 0; + +/** + * Height of workspace. + * @private + */ +Blockly.Mutator.prototype.workspaceHeight_ = 0; + +/** + * Draw the mutator icon. + * @param {!Element} group The icon group. + * @private + */ +Blockly.Mutator.prototype.drawIcon_ = function(group) { + // Square with rounded corners. + Blockly.createSvgElement('rect', + {'class': 'blocklyIconShape', + 'rx': '4', 'ry': '4', + 'height': '16', 'width': '16'}, + group); + // Gear teeth. + Blockly.createSvgElement('path', + {'class': 'blocklyIconSymbol', + 'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 -0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z'}, + group); + // Axle hole. + Blockly.createSvgElement('circle', + {'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'}, + group); +}; + +/** + * Clicking on the icon toggles if the mutator bubble is visible. + * Disable if block is uneditable. + * @param {!Event} e Mouse click event. + * @private + * @override + */ +Blockly.Mutator.prototype.iconClick_ = function(e) { + if (this.block_.isEditable()) { + Blockly.Icon.prototype.iconClick_.call(this, e); + } +}; + +/** + * Create the editor for the mutator's bubble. + * @return {!Element} The top-level node of the editor. + * @private + */ +Blockly.Mutator.prototype.createEditor_ = function() { + /* Create the editor. Here's the markup that will be generated: + + [Workspace] + + */ + this.svgDialog_ = Blockly.createSvgElement('svg', + {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH}, + null); + // Convert the list of names into a list of XML objects for the flyout. + if (this.quarkNames_.length) { + var quarkXml = goog.dom.createDom('xml'); + for (var i = 0, quarkName; quarkName = this.quarkNames_[i]; i++) { + quarkXml.appendChild(goog.dom.createDom('block', {'type': quarkName})); + } + } else { + var quarkXml = null; + } + var workspaceOptions = { + languageTree: quarkXml, + parentWorkspace: this.block_.workspace, + pathToMedia: this.block_.workspace.options.pathToMedia, + RTL: this.block_.RTL, + toolboxPosition: this.block_.RTL ? Blockly.TOOLBOX_AT_RIGHT : + Blockly.TOOLBOX_AT_LEFT, + horizontalLayout: false, + getMetrics: this.getFlyoutMetrics_.bind(this), + setMetrics: null + }; + this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); + this.workspace_.isMutator = true; + this.svgDialog_.appendChild( + this.workspace_.createDom('blocklyMutatorBackground')); + return this.svgDialog_; +}; + +/** + * Add or remove the UI indicating if this icon may be clicked or not. + */ +Blockly.Mutator.prototype.updateEditable = function() { + if (!this.block_.isInFlyout) { + if (this.block_.isEditable()) { + if (this.iconGroup_) { + Blockly.removeClass_(/** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } + } else { + // Close any mutator bubble. Icon is not clickable. + this.setVisible(false); + if (this.iconGroup_) { + Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } + } + } + // Default behaviour for an icon. + Blockly.Icon.prototype.updateEditable.call(this); +}; + +/** + * Callback function triggered when the bubble has resized. + * Resize the workspace accordingly. + * @private + */ +Blockly.Mutator.prototype.resizeBubble_ = function() { + var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH; + var workspaceSize = this.workspace_.getCanvas().getBBox(); + var width; + if (this.block_.RTL) { + width = -workspaceSize.x; + } else { + width = workspaceSize.width + workspaceSize.x; + } + var height = workspaceSize.height + doubleBorderWidth * 3; + if (this.workspace_.flyout_) { + var flyoutMetrics = this.workspace_.flyout_.getMetrics_(); + height = Math.max(height, flyoutMetrics.contentHeight + 20); + } + width += doubleBorderWidth * 3; + // Only resize if the size difference is significant. Eliminates shuddering. + if (Math.abs(this.workspaceWidth_ - width) > doubleBorderWidth || + Math.abs(this.workspaceHeight_ - height) > doubleBorderWidth) { + // Record some layout information for getFlyoutMetrics_. + this.workspaceWidth_ = width; + this.workspaceHeight_ = height; + // Resize the bubble. + this.bubble_.setBubbleSize(width + doubleBorderWidth, + height + doubleBorderWidth); + this.svgDialog_.setAttribute('width', this.workspaceWidth_); + this.svgDialog_.setAttribute('height', this.workspaceHeight_); + } + + if (this.block_.RTL) { + // Scroll the workspace to always left-align. + var translation = 'translate(' + this.workspaceWidth_ + ',0)'; + this.workspace_.getCanvas().setAttribute('transform', translation); + } + this.workspace_.resize(); +}; + +/** + * Show or hide the mutator bubble. + * @param {boolean} visible True if the bubble should be visible. + */ +Blockly.Mutator.prototype.setVisible = function(visible) { + if (visible == this.isVisible()) { + // No change. + return; + } + Blockly.Events.fire( + new Blockly.Events.Ui(this.block_, 'mutatorOpen', !visible, visible)); + if (visible) { + // Create the bubble. + this.bubble_ = new Blockly.Bubble( + /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace), + this.createEditor_(), this.block_.svgPath_, this.iconXY_, null, null); + var tree = this.workspace_.options.languageTree; + if (tree) { + this.workspace_.flyout_.init(this.workspace_); + this.workspace_.flyout_.show(tree.childNodes); + } + + this.rootBlock_ = this.block_.decompose(this.workspace_); + var blocks = this.rootBlock_.getDescendants(); + for (var i = 0, child; child = blocks[i]; i++) { + child.render(); + } + // The root block should not be dragable or deletable. + this.rootBlock_.setMovable(false); + this.rootBlock_.setDeletable(false); + if (this.workspace_.flyout_) { + var margin = this.workspace_.flyout_.CORNER_RADIUS * 2; + var x = this.workspace_.flyout_.width_ + margin; + } else { + var margin = 16; + var x = margin; + } + if (this.block_.RTL) { + x = -x; + } + this.rootBlock_.moveBy(x, margin); + // Save the initial connections, then listen for further changes. + if (this.block_.saveConnections) { + var thisMutator = this; + this.block_.saveConnections(this.rootBlock_); + this.sourceListener_ = function() { + thisMutator.block_.saveConnections(thisMutator.rootBlock_); + }; + this.block_.workspace.addChangeListener(this.sourceListener_); + } + this.resizeBubble_(); + // When the mutator's workspace changes, update the source block. + this.workspace_.addChangeListener(this.workspaceChanged_.bind(this)); + this.updateColour(); + } else { + // Dispose of the bubble. + this.svgDialog_ = null; + this.workspace_.dispose(); + this.workspace_ = null; + this.rootBlock_ = null; + this.bubble_.dispose(); + this.bubble_ = null; + this.workspaceWidth_ = 0; + this.workspaceHeight_ = 0; + if (this.sourceListener_) { + this.block_.workspace.removeChangeListener(this.sourceListener_); + this.sourceListener_ = null; + } + } +}; + +/** + * Update the source block when the mutator's blocks are changed. + * Bump down any block that's too high. + * Fired whenever a change is made to the mutator's workspace. + * @private + */ +Blockly.Mutator.prototype.workspaceChanged_ = function() { + if (Blockly.dragMode_ == Blockly.DRAG_NONE) { + var blocks = this.workspace_.getTopBlocks(false); + var MARGIN = 20; + for (var b = 0, block; block = blocks[b]; b++) { + var blockXY = block.getRelativeToSurfaceXY(); + var blockHW = block.getHeightWidth(); + if (blockXY.y + blockHW.height < MARGIN) { + // Bump any block that's above the top back inside. + block.moveBy(0, MARGIN - blockHW.height - blockXY.y); + } + } + } + + // When the mutator's workspace changes, update the source block. + if (this.rootBlock_.workspace == this.workspace_) { + Blockly.Events.setGroup(true); + var block = this.block_; + var oldMutationDom = block.mutationToDom(); + var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom); + // Switch off rendering while the source block is rebuilt. + var savedRendered = block.rendered; + block.rendered = false; + // Allow the source block to rebuild itself. + block.compose(this.rootBlock_); + // Restore rendering and show the changes. + block.rendered = savedRendered; + // Mutation may have added some elements that need initalizing. + block.initSvg(); + var newMutationDom = block.mutationToDom(); + var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom); + if (oldMutation != newMutation) { + Blockly.Events.fire(new Blockly.Events.Change( + block, 'mutation', null, oldMutation, newMutation)); + // Ensure that any bump is part of this mutation's event group. + var group = Blockly.Events.getGroup(); + setTimeout(function() { + Blockly.Events.setGroup(group); + block.bumpNeighbours_(); + Blockly.Events.setGroup(false); + }, Blockly.BUMP_DELAY); + } + if (block.rendered) { + block.render(); + } + this.resizeBubble_(); + Blockly.Events.setGroup(false); + } +}; + +/** + * Return an object with all the metrics required to size scrollbars for the + * mutator flyout. The following properties are computed: + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .absoluteTop: Top-edge of view. + * .absoluteLeft: Left-edge of view. + * @return {!Object} Contains size and position metrics of mutator dialog's + * workspace. + * @private + */ +Blockly.Mutator.prototype.getFlyoutMetrics_ = function() { + return { + viewHeight: this.workspaceHeight_, + viewWidth: this.workspaceWidth_, + absoluteTop: 0, + absoluteLeft: 0 + }; +}; + +/** + * Dispose of this mutator. + */ +Blockly.Mutator.prototype.dispose = function() { + this.block_.mutator = null; + Blockly.Icon.prototype.dispose.call(this); +}; + +/** + * Reconnect an block to a mutated input. + * @param {Blockly.Connection} connectionChild Connection on child block. + * @param {!Blockly.Block} block Parent block. + * @param {string} inputName Name of input on parent block. + * @return {boolean} True iff a reconnection was made, false otherwise. + */ +Blockly.Mutator.reconnect = function(connectionChild, block, inputName) { + if (!connectionChild || !connectionChild.getSourceBlock().workspace) { + return false; // No connection or block has been deleted. + } + var connectionParent = block.getInput(inputName).connection; + var currentParent = connectionChild.targetBlock(); + if ((!currentParent || currentParent == block) && + connectionParent.targetConnection != connectionChild) { + if (connectionParent.isConnected()) { + // There's already something connected here. Get rid of it. + connectionParent.disconnect(); + } + connectionParent.connect(connectionChild); + return true; + } + return false; +}; + +// Export symbols that would otherwise be renamed by Closure compiler. +if (!goog.global['Blockly']) { + goog.global['Blockly'] = {}; +} +if (!goog.global['Blockly']['Mutator']) { + goog.global['Blockly']['Mutator'] = {}; +} +goog.global['Blockly']['Mutator']['reconnect'] = Blockly.Mutator.reconnect; diff --git a/src/blockly/core/names.js b/src/blockly/core/names.js new file mode 100644 index 0000000..bfe942a --- /dev/null +++ b/src/blockly/core/names.js @@ -0,0 +1,143 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Utility functions for handling variables and procedure names. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Names'); + + +/** + * Class for a database of entity names (variables, functions, etc). + * @param {string} reservedWords A comma-separated string of words that are + * illegal for use as names in a language (e.g. 'new,if,this,...'). + * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace + * before all variable names. + * @constructor + */ +Blockly.Names = function(reservedWords, opt_variablePrefix) { + this.variablePrefix_ = opt_variablePrefix || ''; + this.reservedDict_ = Object.create(null); + if (reservedWords) { + var splitWords = reservedWords.split(','); + for (var i = 0; i < splitWords.length; i++) { + this.reservedDict_[splitWords[i]] = true; + } + } + this.reset(); +}; + +/** + * When JavaScript (or most other languages) is generated, variable 'foo' and + * procedure 'foo' would collide. However, Blockly has no such problems since + * variable get 'foo' and procedure call 'foo' are unambiguous. + * Therefore, Blockly keeps a separate type name to disambiguate. + * getName('foo', 'variable') -> 'foo' + * getName('foo', 'procedure') -> 'foo2' + */ + +/** + * Empty the database and start from scratch. The reserved words are kept. + */ +Blockly.Names.prototype.reset = function() { + this.db_ = Object.create(null); + this.dbReverse_ = Object.create(null); +}; + +/** + * Convert a Blockly entity name to a legal exportable entity name. + * @param {string} name The Blockly entity name (no constraints). + * @param {string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). + * @return {string} An entity name legal for the exported language. + */ +Blockly.Names.prototype.getName = function(name, type) { + var normalized = name.toLowerCase() + '_' + type; + var prefix = (type == Blockly.Variables.NAME_TYPE) ? + this.variablePrefix_ : ''; + if (normalized in this.db_) { + return prefix + this.db_[normalized]; + } + var safeName = this.getDistinctName(name, type); + this.db_[normalized] = safeName.substr(prefix.length); + return safeName; +}; + +/** + * Convert a Blockly entity name to a legal exportable entity name. + * Ensure that this is a new name not overlapping any previously defined name. + * Also check against list of reserved words for the current language and + * ensure name doesn't collide. + * @param {string} name The Blockly entity name (no constraints). + * @param {string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). + * @return {string} An entity name legal for the exported language. + */ +Blockly.Names.prototype.getDistinctName = function(name, type) { + var safeName = this.safeName_(name); + var i = ''; + while (this.dbReverse_[safeName + i] || + (safeName + i) in this.reservedDict_) { + // Collision with existing name. Create a unique name. + i = i ? i + 1 : 2; + } + safeName += i; + this.dbReverse_[safeName] = true; + var prefix = (type == Blockly.Variables.NAME_TYPE) ? + this.variablePrefix_ : ''; + return prefix + safeName; +}; + +/** + * Given a proposed entity name, generate a name that conforms to the + * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for + * variables. + * @param {string} name Potentially illegal entity name. + * @return {string} Safe entity name. + * @private + */ +Blockly.Names.prototype.safeName_ = function(name) { + if (!name) { + name = 'unnamed'; + } else { + // Unfortunately names in non-latin characters will look like + // _E9_9F_B3_E4_B9_90 which is pretty meaningless. + name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_'); + // Most languages don't allow names with leading numbers. + if ('0123456789'.indexOf(name[0]) != -1) { + name = 'my_' + name; + } + } + return name; +}; + +/** + * Do the given two entity names refer to the same entity? + * Blockly names are case-insensitive. + * @param {string} name1 First name. + * @param {string} name2 Second name. + * @return {boolean} True if names are the same. + */ +Blockly.Names.equals = function(name1, name2) { + return name1.toLowerCase() == name2.toLowerCase(); +}; diff --git a/src/blockly/core/options.js b/src/blockly/core/options.js new file mode 100644 index 0000000..268affa --- /dev/null +++ b/src/blockly/core/options.js @@ -0,0 +1,231 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object that controls settings for the workspace. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Options'); + + +/** + * Parse the user-specified options, using reasonable defaults where behaviour + * is unspecified. + * @param {!Object} options Dictionary of options. Specification: + * https://developers.google.com/blockly/guides/get-started/web#configuration + * @constructor + */ +Blockly.Options = function(options) { + var readOnly = !!options['readOnly']; + if (readOnly) { + var languageTree = null; + var hasCategories = false; + var hasTrashcan = false; + var hasCollapse = false; + var hasComments = false; + var hasDisable = false; + var hasSounds = false; + } else { + var languageTree = Blockly.Options.parseToolboxTree(options['toolbox']); + var hasCategories = Boolean(languageTree && + languageTree.getElementsByTagName('category').length); + var hasTrashcan = options['trashcan']; + if (hasTrashcan === undefined) { + hasTrashcan = hasCategories; + } + var hasCollapse = options['collapse']; + if (hasCollapse === undefined) { + hasCollapse = hasCategories; + } + var hasComments = options['comments']; + if (hasComments === undefined) { + hasComments = hasCategories; + } + var hasDisable = options['disable']; + if (hasDisable === undefined) { + hasDisable = hasCategories; + } + var hasSounds = options['sounds']; + if (hasSounds === undefined) { + hasSounds = true; + } + } + var rtl = !!options['rtl']; + var horizontalLayout = options['horizontalLayout']; + if (horizontalLayout === undefined) { + horizontalLayout = false; + } + var toolboxAtStart = options['toolboxPosition']; + if (toolboxAtStart === 'end') { + toolboxAtStart = false; + } else { + toolboxAtStart = true; + } + + if (horizontalLayout) { + var toolboxPosition = toolboxAtStart ? + Blockly.TOOLBOX_AT_TOP : Blockly.TOOLBOX_AT_BOTTOM; + } else { + var toolboxPosition = (toolboxAtStart == rtl) ? + Blockly.TOOLBOX_AT_RIGHT : Blockly.TOOLBOX_AT_LEFT; + } + + var hasScrollbars = options['scrollbars']; + if (hasScrollbars === undefined) { + hasScrollbars = hasCategories; + } + var hasCss = options['css']; + if (hasCss === undefined) { + hasCss = true; + } + var pathToMedia = 'https://blockly-demo.appspot.com/static/media/'; + if (options['media']) { + pathToMedia = options['media']; + } else if (options['path']) { + // 'path' is a deprecated option which has been replaced by 'media'. + pathToMedia = options['path'] + 'media/'; + } + + this.RTL = rtl; + this.collapse = hasCollapse; + this.comments = hasComments; + this.disable = hasDisable; + this.readOnly = readOnly; + this.maxBlocks = options['maxBlocks'] || Infinity; + this.pathToMedia = pathToMedia; + this.hasCategories = hasCategories; + this.hasScrollbars = hasScrollbars; + this.hasTrashcan = hasTrashcan; + this.hasSounds = hasSounds; + this.hasCss = hasCss; + this.horizontalLayout = horizontalLayout; + this.languageTree = languageTree; + this.gridOptions = Blockly.Options.parseGridOptions_(options); + this.zoomOptions = Blockly.Options.parseZoomOptions_(options); + this.toolboxPosition = toolboxPosition; +}; + +/** + * @type {Blockly.Workspace} the parent of the current workspace, or null if + * there is no parent workspace. + **/ +Blockly.Options.prototype.parentWorkspace = null; + +/** + * If set, sets the translation of the workspace to match the scrollbars. + */ +Blockly.Options.prototype.setMetrics = null; + +/** + * Return an object with the metrics required to size the workspace. + * @return {Object} Contains size and position metrics, or null. + */ +Blockly.Options.prototype.getMetrics = null; + +/** + * Parse the user-specified zoom options, using reasonable defaults where + * behaviour is unspecified. See zoom documentation: + * https://developers.google.com/blockly/guides/configure/web/zoom + * @param {!Object} options Dictionary of options. + * @return {!Object} A dictionary of normalized options. + * @private + */ +Blockly.Options.parseZoomOptions_ = function(options) { + var zoom = options['zoom'] || {}; + var zoomOptions = {}; + if (zoom['controls'] === undefined) { + zoomOptions.controls = false; + } else { + zoomOptions.controls = !!zoom['controls']; + } + if (zoom['wheel'] === undefined) { + zoomOptions.wheel = false; + } else { + zoomOptions.wheel = !!zoom['wheel']; + } + if (zoom['startScale'] === undefined) { + zoomOptions.startScale = 1; + } else { + zoomOptions.startScale = parseFloat(zoom['startScale']); + } + if (zoom['maxScale'] === undefined) { + zoomOptions.maxScale = 3; + } else { + zoomOptions.maxScale = parseFloat(zoom['maxScale']); + } + if (zoom['minScale'] === undefined) { + zoomOptions.minScale = 0.3; + } else { + zoomOptions.minScale = parseFloat(zoom['minScale']); + } + if (zoom['scaleSpeed'] === undefined) { + zoomOptions.scaleSpeed = 1.2; + } else { + zoomOptions.scaleSpeed = parseFloat(zoom['scaleSpeed']); + } + return zoomOptions; +}; + +/** + * Parse the user-specified grid options, using reasonable defaults where + * behaviour is unspecified. See grid documentation: + * https://developers.google.com/blockly/guides/configure/web/grid + * @param {!Object} options Dictionary of options. + * @return {!Object} A dictionary of normalized options. + * @private + */ +Blockly.Options.parseGridOptions_ = function(options) { + var grid = options['grid'] || {}; + var gridOptions = {}; + gridOptions.spacing = parseFloat(grid['spacing']) || 0; + gridOptions.colour = grid['colour'] || '#888'; + gridOptions.length = parseFloat(grid['length']) || 1; + gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap']; + return gridOptions; +}; + +/** + * Parse the provided toolbox tree into a consistent DOM format. + * @param {Node|string} tree DOM tree of blocks, or text representation of same. + * @return {Node} DOM tree of blocks, or null. + */ +Blockly.Options.parseToolboxTree = function(tree) { + if (tree) { + if (typeof tree != 'string') { + if (typeof XSLTProcessor == 'undefined' && tree.outerHTML) { + // In this case the tree will not have been properly built by the + // browser. The HTML will be contained in the element, but it will + // not have the proper DOM structure since the browser doesn't support + // XSLTProcessor (XML -> HTML). This is the case in IE 9+. + tree = tree.outerHTML; + } else if (!(tree instanceof Element)) { + tree = null; + } + } + if (typeof tree == 'string') { + tree = Blockly.Xml.textToDom(tree); + } + } else { + tree = null; + } + return tree; +}; diff --git a/src/blockly/core/procedures.js b/src/blockly/core/procedures.js new file mode 100644 index 0000000..beb4a17 --- /dev/null +++ b/src/blockly/core/procedures.js @@ -0,0 +1,287 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Utility functions for handling procedures. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Procedures'); + +goog.require('Blockly.Blocks'); +goog.require('Blockly.Field'); +goog.require('Blockly.Names'); +goog.require('Blockly.Workspace'); + + +/** + * Category to separate procedure names from variables and generated functions. + */ +Blockly.Procedures.NAME_TYPE = 'PROCEDURE'; + +/** + * Find all user-created procedure definitions in a workspace. + * @param {!Blockly.Workspace} root Root workspace. + * @return {!Array.>} Pair of arrays, the + * first contains procedures without return variables, the second with. + * Each procedure is defined by a three-element list of name, parameter + * list, and return value boolean. + */ +Blockly.Procedures.allProcedures = function(root) { + var blocks = root.getAllBlocks(); + var proceduresReturn = []; + var proceduresNoReturn = []; + for (var i = 0; i < blocks.length; i++) { + if (blocks[i].getProcedureDef) { + var tuple = blocks[i].getProcedureDef(); + if (tuple) { + if (tuple[2]) { + proceduresReturn.push(tuple); + } else { + proceduresNoReturn.push(tuple); + } + } + } + } + proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_); + proceduresReturn.sort(Blockly.Procedures.procTupleComparator_); + return [proceduresNoReturn, proceduresReturn]; +}; + +/** + * Comparison function for case-insensitive sorting of the first element of + * a tuple. + * @param {!Array} ta First tuple. + * @param {!Array} tb Second tuple. + * @return {number} -1, 0, or 1 to signify greater than, equality, or less than. + * @private + */ +Blockly.Procedures.procTupleComparator_ = function(ta, tb) { + return ta[0].toLowerCase().localeCompare(tb[0].toLowerCase()); +}; + +/** + * Ensure two identically-named procedures don't exist. + * @param {string} name Proposed procedure name. + * @param {!Blockly.Block} block Block to disambiguate. + * @return {string} Non-colliding name. + */ +Blockly.Procedures.findLegalName = function(name, block) { + if (block.isInFlyout) { + // Flyouts can have multiple procedures called 'do something'. + return name; + } + while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) { + // Collision with another procedure. + var r = name.match(/^(.*?)(\d+)$/); + if (!r) { + name += '2'; + } else { + name = r[1] + (parseInt(r[2], 10) + 1); + } + } + return name; +}; + +/** + * Does this procedure have a legal name? Illegal names include names of + * procedures already defined. + * @param {string} name The questionable name. + * @param {!Blockly.Workspace} workspace The workspace to scan for collisions. + * @param {Blockly.Block=} opt_exclude Optional block to exclude from + * comparisons (one doesn't want to collide with oneself). + * @return {boolean} True if the name is legal. + * @private + */ +Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) { + var blocks = workspace.getAllBlocks(); + // Iterate through every block and check the name. + for (var i = 0; i < blocks.length; i++) { + if (blocks[i] == opt_exclude) { + continue; + } + if (blocks[i].getProcedureDef) { + var procName = blocks[i].getProcedureDef(); + if (Blockly.Names.equals(procName[0], name)) { + return false; + } + } + } + return true; +}; + +/** + * Rename a procedure. Called by the editable field. + * @param {string} name The proposed new name. + * @return {string} The accepted name. + * @this {!Blockly.Field} + */ +Blockly.Procedures.rename = function(name) { + // Strip leading and trailing whitespace. Beyond this, all names are legal. + name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); + + // Ensure two identically-named procedures don't exist. + var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_); + var oldName = this.text_; + if (oldName != name && oldName != legalName) { + // Rename any callers. + var blocks = this.sourceBlock_.workspace.getAllBlocks(); + for (var i = 0; i < blocks.length; i++) { + if (blocks[i].renameProcedure) { + blocks[i].renameProcedure(oldName, legalName); + } + } + } + return legalName; +}; + +/** + * Construct the blocks required by the flyout for the procedure category. + * @param {!Blockly.Workspace} workspace The workspace contianing procedures. + * @return {!Array.} Array of XML block elements. + */ +Blockly.Procedures.flyoutCategory = function(workspace) { + var xmlList = []; + if (Blockly.Blocks['procedures_defnoreturn']) { + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', 'procedures_defnoreturn'); + block.setAttribute('gap', 16); + xmlList.push(block); + } + if (Blockly.Blocks['procedures_defreturn']) { + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', 'procedures_defreturn'); + block.setAttribute('gap', 16); + xmlList.push(block); + } + if (Blockly.Blocks['procedures_ifreturn']) { + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', 'procedures_ifreturn'); + block.setAttribute('gap', 16); + xmlList.push(block); + } + if (xmlList.length) { + // Add slightly larger gap between system blocks and user calls. + xmlList[xmlList.length - 1].setAttribute('gap', 24); + } + + function populateProcedures(procedureList, templateName) { + for (var i = 0; i < procedureList.length; i++) { + var name = procedureList[i][0]; + var args = procedureList[i][1]; + // + // + // + // + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', templateName); + block.setAttribute('gap', 16); + var mutation = goog.dom.createDom('mutation'); + mutation.setAttribute('name', name); + block.appendChild(mutation); + for (var j = 0; j < args.length; j++) { + var arg = goog.dom.createDom('arg'); + arg.setAttribute('name', args[j]); + mutation.appendChild(arg); + } + xmlList.push(block); + } + } + + var tuple = Blockly.Procedures.allProcedures(workspace); + populateProcedures(tuple[0], 'procedures_callnoreturn'); + populateProcedures(tuple[1], 'procedures_callreturn'); + return xmlList; +}; + +/** + * Find all the callers of a named procedure. + * @param {string} name Name of procedure. + * @param {!Blockly.Workspace} workspace The workspace to find callers in. + * @return {!Array.} Array of caller blocks. + */ +Blockly.Procedures.getCallers = function(name, workspace) { + var callers = []; + var blocks = workspace.getAllBlocks(); + // Iterate through every block and check the name. + for (var i = 0; i < blocks.length; i++) { + if (blocks[i].getProcedureCall) { + var procName = blocks[i].getProcedureCall(); + // Procedure name may be null if the block is only half-built. + if (procName && Blockly.Names.equals(procName, name)) { + callers.push(blocks[i]); + } + } + } + return callers; +}; + +/** + * When a procedure definition changes its parameters, find and edit all its + * callers. + * @param {!Blockly.Block} defBlock Procedure definition block. + */ +Blockly.Procedures.mutateCallers = function(defBlock) { + var oldRecordUndo = Blockly.Events.recordUndo; + var name = defBlock.getProcedureDef()[0]; + var xmlElement = defBlock.mutationToDom(true); + var callers = Blockly.Procedures.getCallers(name, defBlock.workspace); + for (var i = 0, caller; caller = callers[i]; i++) { + var oldMutationDom = caller.mutationToDom(); + var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom); + caller.domToMutation(xmlElement); + var newMutationDom = caller.mutationToDom(); + var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom); + if (oldMutation != newMutation) { + // Fire a mutation on every caller block. But don't record this as an + // undo action since it is deterministically tied to the procedure's + // definition mutation. + Blockly.Events.recordUndo = false; + Blockly.Events.fire(new Blockly.Events.Change( + caller, 'mutation', null, oldMutation, newMutation)); + Blockly.Events.recordUndo = oldRecordUndo; + } + } +}; + +/** + * Find the definition block for the named procedure. + * @param {string} name Name of procedure. + * @param {!Blockly.Workspace} workspace The workspace to search. + * @return {Blockly.Block} The procedure definition block, or null not found. + */ +Blockly.Procedures.getDefinition = function(name, workspace) { + // Assume that a procedure definition is a top block. + var blocks = workspace.getTopBlocks(false); + for (var i = 0; i < blocks.length; i++) { + if (blocks[i].getProcedureDef) { + var tuple = blocks[i].getProcedureDef(); + if (tuple && Blockly.Names.equals(tuple[0], name)) { + return blocks[i]; + } + } + } + return null; +}; diff --git a/src/blockly/core/rendered_connection.js b/src/blockly/core/rendered_connection.js new file mode 100644 index 0000000..dcb90f1 --- /dev/null +++ b/src/blockly/core/rendered_connection.js @@ -0,0 +1,395 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Components for creating connections between blocks. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.RenderedConnection'); + +goog.require('Blockly.Connection'); + + +/** + * Class for a connection between blocks that may be rendered on screen. + * @param {!Blockly.Block} source The block establishing this connection. + * @param {number} type The type of the connection. + * @extends {Blockly.Connection} + * @constructor + */ +Blockly.RenderedConnection = function(source, type) { + Blockly.RenderedConnection.superClass_.constructor.call(this, source, type); + this.offsetInBlock_ = new goog.math.Coordinate(0, 0); +}; +goog.inherits(Blockly.RenderedConnection, Blockly.Connection); + +/** + * Returns the distance between this connection and another connection. + * @param {!Blockly.Connection} otherConnection The other connection to measure + * the distance to. + * @return {number} The distance between connections. + */ +Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) { + var xDiff = this.x_ - otherConnection.x_; + var yDiff = this.y_ - otherConnection.y_; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff); +}; + +/** + * Move the block(s) belonging to the connection to a point where they don't + * visually interfere with the specified connection. + * @param {!Blockly.Connection} staticConnection The connection to move away + * from. + * @private + */ +Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) { + if (Blockly.dragMode_ != Blockly.DRAG_NONE) { + // Don't move blocks around while the user is doing the same. + return; + } + // Move the root block. + var rootBlock = this.sourceBlock_.getRootBlock(); + if (rootBlock.isInFlyout) { + // Don't move blocks around in a flyout. + return; + } + var reverse = false; + if (!rootBlock.isMovable()) { + // Can't bump an uneditable block away. + // Check to see if the other block is movable. + rootBlock = staticConnection.getSourceBlock().getRootBlock(); + if (!rootBlock.isMovable()) { + return; + } + // Swap the connections and move the 'static' connection instead. + staticConnection = this; + reverse = true; + } + // Raise it to the top for extra visibility. + var selected = Blockly.selected == rootBlock; + selected || rootBlock.addSelect(); + var dx = (staticConnection.x_ + Blockly.SNAP_RADIUS) - this.x_; + var dy = (staticConnection.y_ + Blockly.SNAP_RADIUS) - this.y_; + if (reverse) { + // When reversing a bump due to an uneditable block, bump up. + dy = -dy; + } + if (rootBlock.RTL) { + dx = -dx; + } + rootBlock.moveBy(dx, dy); + selected || rootBlock.removeSelect(); +}; + +/** + * Change the connection's coordinates. + * @param {number} x New absolute x coordinate. + * @param {number} y New absolute y coordinate. + */ +Blockly.RenderedConnection.prototype.moveTo = function(x, y) { + // Remove it from its old location in the database (if already present) + if (this.inDB_) { + this.db_.removeConnection_(this); + } + this.x_ = x; + this.y_ = y; + // Insert it into its new location in the database. + if (!this.hidden_) { + this.db_.addConnection(this); + } +}; + +/** + * Change the connection's coordinates. + * @param {number} dx Change to x coordinate. + * @param {number} dy Change to y coordinate. + */ +Blockly.RenderedConnection.prototype.moveBy = function(dx, dy) { + this.moveTo(this.x_ + dx, this.y_ + dy); +}; + +/** + * Move this connection to the location given by its offset within the block and + * the coordinate of the block's top left corner. + * @param {!goog.math.Coordinate} blockTL The coordinate of the top left corner + * of the block. + */ +Blockly.RenderedConnection.prototype.moveToOffset = function(blockTL) { + this.moveTo(blockTL.x + this.offsetInBlock_.x, + blockTL.y + this.offsetInBlock_.y); +}; + +/** + * Set the offset of this connection relative to the top left of its block. + * @param {number} x The new relative x. + * @param {number} y The new relative y. + */ +Blockly.RenderedConnection.prototype.setOffsetInBlock = function(x, y) { + this.offsetInBlock_.x = x; + this.offsetInBlock_.y = y; +}; + +/** + * Move the blocks on either side of this connection right next to each other. + * @private + */ +Blockly.RenderedConnection.prototype.tighten_ = function() { + var dx = this.targetConnection.x_ - this.x_; + var dy = this.targetConnection.y_ - this.y_; + if (dx != 0 || dy != 0) { + var block = this.targetBlock(); + var svgRoot = block.getSvgRoot(); + if (!svgRoot) { + throw 'block is not rendered.'; + } + var xy = Blockly.getRelativeXY_(svgRoot); + block.getSvgRoot().setAttribute('transform', + 'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')'); + block.moveConnections_(-dx, -dy); + } +}; + +/** + * Find the closest compatible connection to this connection. + * @param {number} maxLimit The maximum radius to another connection. + * @param {number} dx Horizontal offset between this connection's location + * in the database and the current location (as a result of dragging). + * @param {number} dy Vertical offset between this connection's location + * in the database and the current location (as a result of dragging). + * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two + * properties: 'connection' which is either another connection or null, + * and 'radius' which is the distance. + */ +Blockly.RenderedConnection.prototype.closest = function(maxLimit, dx, dy) { + return this.dbOpposite_.searchForClosest(this, maxLimit, dx, dy); +}; + +/** + * Add highlighting around this connection. + */ +Blockly.RenderedConnection.prototype.highlight = function() { + var steps; + if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) { + steps = 'm 0,0 ' + Blockly.BlockSvg.TAB_PATH_DOWN + ' v 5'; + } else { + steps = 'm -20,0 h 5 ' + Blockly.BlockSvg.NOTCH_PATH_LEFT + ' h 5'; + } + var xy = this.sourceBlock_.getRelativeToSurfaceXY(); + var x = this.x_ - xy.x; + var y = this.y_ - xy.y; + Blockly.Connection.highlightedPath_ = Blockly.createSvgElement('path', + {'class': 'blocklyHighlightedConnectionPath', + 'd': steps, + transform: 'translate(' + x + ',' + y + ')' + + (this.sourceBlock_.RTL ? ' scale(-1 1)' : '')}, + this.sourceBlock_.getSvgRoot()); +}; + +/** + * Unhide this connection, as well as all down-stream connections on any block + * attached to this connection. This happens when a block is expanded. + * Also unhides down-stream comments. + * @return {!Array.} List of blocks to render. + */ +Blockly.RenderedConnection.prototype.unhideAll = function() { + this.setHidden(false); + // All blocks that need unhiding must be unhidden before any rendering takes + // place, since rendering requires knowing the dimensions of lower blocks. + // Also, since rendering a block renders all its parents, we only need to + // render the leaf nodes. + var renderList = []; + if (this.type != Blockly.INPUT_VALUE && this.type != Blockly.NEXT_STATEMENT) { + // Only spider down. + return renderList; + } + var block = this.targetBlock(); + if (block) { + var connections; + if (block.isCollapsed()) { + // This block should only be partially revealed since it is collapsed. + connections = []; + block.outputConnection && connections.push(block.outputConnection); + block.nextConnection && connections.push(block.nextConnection); + block.previousConnection && connections.push(block.previousConnection); + } else { + // Show all connections of this block. + connections = block.getConnections_(true); + } + for (var i = 0; i < connections.length; i++) { + renderList.push.apply(renderList, connections[i].unhideAll()); + } + if (!renderList.length) { + // Leaf block. + renderList[0] = block; + } + } + return renderList; +}; + +/** + * Remove the highlighting around this connection. + */ +Blockly.RenderedConnection.prototype.unhighlight = function() { + goog.dom.removeNode(Blockly.Connection.highlightedPath_); + delete Blockly.Connection.highlightedPath_; +}; + +/** + * Set whether this connections is hidden (not tracked in a database) or not. + * @param {boolean} hidden True if connection is hidden. + */ +Blockly.RenderedConnection.prototype.setHidden = function(hidden) { + this.hidden_ = hidden; + if (hidden && this.inDB_) { + this.db_.removeConnection_(this); + } else if (!hidden && !this.inDB_) { + this.db_.addConnection(this); + } +}; + +/** + * Hide this connection, as well as all down-stream connections on any block + * attached to this connection. This happens when a block is collapsed. + * Also hides down-stream comments. + */ +Blockly.RenderedConnection.prototype.hideAll = function() { + this.setHidden(true); + if (this.targetConnection) { + var blocks = this.targetBlock().getDescendants(); + for (var i = 0; i < blocks.length; i++) { + var block = blocks[i]; + // Hide all connections of all children. + var connections = block.getConnections_(true); + for (var j = 0; j < connections.length; j++) { + connections[j].setHidden(true); + } + // Close all bubbles of all children. + var icons = block.getIcons(); + for (var j = 0; j < icons.length; j++) { + icons[j].setVisible(false); + } + } + } +}; + +/** + * Check if the two connections can be dragged to connect to each other. + * @param {!Blockly.Connection} candidate A nearby connection to check. + * @param {number} maxRadius The maximum radius allowed for connections. + * @return {boolean} True if the connection is allowed, false otherwise. + */ +Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate, + maxRadius) { + if (this.distanceFrom(candidate) > maxRadius) { + return false; + } + + return Blockly.RenderedConnection.superClass_.isConnectionAllowed.call(this, + candidate); +}; + +/** + * Disconnect two blocks that are connected by this connection. + * @param {!Blockly.Block} parentBlock The superior block. + * @param {!Blockly.Block} childBlock The inferior block. + * @private + */ +Blockly.RenderedConnection.prototype.disconnectInternal_ = function(parentBlock, + childBlock) { + Blockly.RenderedConnection.superClass_.disconnectInternal_.call(this, + parentBlock, childBlock); + // Rerender the parent so that it may reflow. + if (parentBlock.rendered) { + parentBlock.render(); + } + if (childBlock.rendered) { + childBlock.updateDisabled(); + childBlock.render(); + } +}; + +/** + * Respawn the shadow block if there was one connected to the this connection. + * Render/rerender blocks as needed. + * @private + */ +Blockly.RenderedConnection.prototype.respawnShadow_ = function() { + var parentBlock = this.getSourceBlock(); + // Respawn the shadow block if there is one. + var shadow = this.getShadowDom(); + if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) { + Blockly.RenderedConnection.superClass_.respawnShadow_.call(this); + var blockShadow = this.targetBlock(); + if (!blockShadow) { + throw 'Couldn\'t respawn the shadow block that should exist here.'; + } + blockShadow.initSvg(); + blockShadow.render(false); + if (parentBlock.rendered) { + parentBlock.render(); + } + } +}; + +/** + * Find all nearby compatible connections to this connection. + * Type checking does not apply, since this function is used for bumping. + * @param {number} maxLimit The maximum radius to another connection. + * @return {!Array.} List of connections. + * @private + */ +Blockly.RenderedConnection.prototype.neighbours_ = function(maxLimit) { + return this.dbOpposite_.getNeighbours(this, maxLimit); +}; + +/** + * Connect two connections together. This is the connection on the superior + * block. Rerender blocks as needed. + * @param {!Blockly.Connection} childConnection Connection on inferior block. + * @private + */ +Blockly.RenderedConnection.prototype.connect_ = function(childConnection) { + Blockly.RenderedConnection.superClass_.connect_.call(this, childConnection); + + var parentConnection = this; + var parentBlock = parentConnection.getSourceBlock(); + var childBlock = childConnection.getSourceBlock(); + + if (parentBlock.rendered) { + parentBlock.updateDisabled(); + } + if (childBlock.rendered) { + childBlock.updateDisabled(); + } + if (parentBlock.rendered && childBlock.rendered) { + if (parentConnection.type == Blockly.NEXT_STATEMENT || + parentConnection.type == Blockly.PREVIOUS_STATEMENT) { + // Child block may need to square off its corners if it is in a stack. + // Rendering a child will render its parent. + childBlock.render(); + } else { + // Child block does not change shape. Rendering the parent node will + // move its connected children into position. + parentBlock.render(); + } + } +}; diff --git a/src/blockly/core/scrollbar.js b/src/blockly/core/scrollbar.js new file mode 100644 index 0000000..da2dd94 --- /dev/null +++ b/src/blockly/core/scrollbar.js @@ -0,0 +1,750 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Library for creating scrollbars. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Scrollbar'); +goog.provide('Blockly.ScrollbarPair'); + +goog.require('goog.dom'); +goog.require('goog.events'); + + +/** + * Class for a pair of scrollbars. Horizontal and vertical. + * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbars to. + * @constructor + */ +Blockly.ScrollbarPair = function(workspace) { + this.workspace_ = workspace; + this.hScroll = new Blockly.Scrollbar(workspace, true, true); + this.vScroll = new Blockly.Scrollbar(workspace, false, true); + this.corner_ = Blockly.createSvgElement('rect', + {'height': Blockly.Scrollbar.scrollbarThickness, + 'width': Blockly.Scrollbar.scrollbarThickness, + 'class': 'blocklyScrollbarBackground'}, null); + Blockly.Scrollbar.insertAfter_(this.corner_, workspace.getBubbleCanvas()); +}; + +/** + * Previously recorded metrics from the workspace. + * @type {Object} + * @private + */ +Blockly.ScrollbarPair.prototype.oldHostMetrics_ = null; + +/** + * Dispose of this pair of scrollbars. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.ScrollbarPair.prototype.dispose = function() { + goog.dom.removeNode(this.corner_); + this.corner_ = null; + this.workspace_ = null; + this.oldHostMetrics_ = null; + this.hScroll.dispose(); + this.hScroll = null; + this.vScroll.dispose(); + this.vScroll = null; +}; + +/** + * Recalculate both of the scrollbars' locations and lengths. + * Also reposition the corner rectangle. + */ +Blockly.ScrollbarPair.prototype.resize = function() { + // Look up the host metrics once, and use for both scrollbars. + var hostMetrics = this.workspace_.getMetrics(); + if (!hostMetrics) { + // Host element is likely not visible. + return; + } + + // Only change the scrollbars if there has been a change in metrics. + var resizeH = false; + var resizeV = false; + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth || + this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight || + this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop || + this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) { + // The window has been resized or repositioned. + resizeH = true; + resizeV = true; + } else { + // Has the content been resized or moved? + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.contentWidth != hostMetrics.contentWidth || + this.oldHostMetrics_.viewLeft != hostMetrics.viewLeft || + this.oldHostMetrics_.contentLeft != hostMetrics.contentLeft) { + resizeH = true; + } + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.contentHeight != hostMetrics.contentHeight || + this.oldHostMetrics_.viewTop != hostMetrics.viewTop || + this.oldHostMetrics_.contentTop != hostMetrics.contentTop) { + resizeV = true; + } + } + if (resizeH) { + this.hScroll.resize(hostMetrics); + } + if (resizeV) { + this.vScroll.resize(hostMetrics); + } + + // Reposition the corner square. + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth || + this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) { + this.corner_.setAttribute('x', this.vScroll.position_.x); + } + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight || + this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop) { + this.corner_.setAttribute('y', this.hScroll.position_.y); + } + + // Cache the current metrics to potentially short-cut the next resize event. + this.oldHostMetrics_ = hostMetrics; +}; + +/** + * Set the sliders of both scrollbars to be at a certain position. + * @param {number} x Horizontal scroll value. + * @param {number} y Vertical scroll value. + */ +Blockly.ScrollbarPair.prototype.set = function(x, y) { + // This function is equivalent to: + // this.hScroll.set(x); + // this.vScroll.set(y); + // However, that calls setMetrics twice which causes a chain of + // getAttribute->setAttribute->getAttribute resulting in an extra layout pass. + // Combining them speeds up rendering. + var xyRatio = {}; + + var hHandlePosition = x * this.hScroll.ratio_; + var vHandlePosition = y * this.vScroll.ratio_; + + var hBarLength = this.hScroll.scrollViewSize_; + var vBarLength = this.vScroll.scrollViewSize_; + + xyRatio.x = this.getRatio_(hHandlePosition, hBarLength); + xyRatio.y = this.getRatio_(vHandlePosition, vBarLength); + this.workspace_.setMetrics(xyRatio); + + this.hScroll.setHandlePosition(hHandlePosition); + this.vScroll.setHandlePosition(vHandlePosition); +}; + +/** + * Helper to calculate the ratio of handle position to scrollbar view size. + * @param {number} handlePosition The value of the handle. + * @param {number} viewSize The total size of the scrollbar's view. + * @return {number} Ratio. + * @private + */ +Blockly.ScrollbarPair.prototype.getRatio_ = function(handlePosition, viewSize) { + var ratio = handlePosition / viewSize; + if (isNaN(ratio)) { + return 0; + } + return ratio; +}; + +// -------------------------------------------------------------------- + +/** + * Class for a pure SVG scrollbar. + * This technique offers a scrollbar that is guaranteed to work, but may not + * look or behave like the system's scrollbars. + * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbar to. + * @param {boolean} horizontal True if horizontal, false if vertical. + * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair. + * @constructor + */ +Blockly.Scrollbar = function(workspace, horizontal, opt_pair) { + this.workspace_ = workspace; + this.pair_ = opt_pair || false; + this.horizontal_ = horizontal; + this.oldHostMetrics_ = null; + + this.createDom_(); + + /** + * The upper left corner of the scrollbar's svg group. + * @type {goog.math.Coordinate} + * @private + */ + this.position_ = new goog.math.Coordinate(0, 0); + + if (horizontal) { + this.svgBackground_.setAttribute('height', + Blockly.Scrollbar.scrollbarThickness); + this.svgHandle_.setAttribute('height', + Blockly.Scrollbar.scrollbarThickness - 5); + this.svgHandle_.setAttribute('y', 2.5); + + this.lengthAttribute_ = 'width'; + this.positionAttribute_ = 'x'; + } else { + this.svgBackground_.setAttribute('width', + Blockly.Scrollbar.scrollbarThickness); + this.svgHandle_.setAttribute('width', + Blockly.Scrollbar.scrollbarThickness - 5); + this.svgHandle_.setAttribute('x', 2.5); + + this.lengthAttribute_ = 'height'; + this.positionAttribute_ = 'y'; + } + var scrollbar = this; + this.onMouseDownBarWrapper_ = Blockly.bindEvent_(this.svgBackground_, + 'mousedown', scrollbar, scrollbar.onMouseDownBar_); + this.onMouseDownHandleWrapper_ = Blockly.bindEvent_(this.svgHandle_, + 'mousedown', scrollbar, scrollbar.onMouseDownHandle_); +}; + +/** + * The size of the area within which the scrollbar handle can move. + * @type {number} + * @private + */ +Blockly.Scrollbar.prototype.scrollViewSize_ = 0; + +/** + * The length of the scrollbar handle. + * @type {number} + * @private + */ +Blockly.Scrollbar.prototype.handleLength_ = 0; + +/** + * The offset of the start of the handle from the start of the scrollbar range. + * @type {number} + * @private + */ +Blockly.Scrollbar.prototype.handlePosition_ = 0; + +/** + * Whether the scrollbar handle is visible. + * @type {boolean} + * @private + */ +Blockly.Scrollbar.prototype.isVisible_ = true; + +/** + * Width of vertical scrollbar or height of horizontal scrollbar. + * Increase the size of scrollbars on touch devices. + * Don't define if there is no document object (e.g. node.js). + */ +Blockly.Scrollbar.scrollbarThickness = 15; +if (goog.events.BrowserFeature.TOUCH_ENABLED) { + Blockly.Scrollbar.scrollbarThickness = 25; +} + +/** + * @param {!Object} first An object containing computed measurements of a + * workspace. + * @param {!Object} second Another object containing computed measurements of a + * workspace. + * @return {boolean} Whether the two sets of metrics are equivalent. + * @private + */ +Blockly.Scrollbar.metricsAreEquivalent_ = function(first, second) { + if (!(first && second)) { + return false; + } + + if (first.viewWidth != second.viewWidth || + first.viewHeight != second.viewHeight || + first.viewLeft != second.viewLeft || + first.viewTop != second.viewTop || + first.absoluteTop != second.absoluteTop || + first.absoluteLeft != second.absoluteLeft || + first.contentWidth != second.contentWidth || + first.contentHeight != second.contentHeight || + first.contentLeft != second.contentLeft || + first.contentTop != second.contentTop) { + return false; + } + + return true; +}; + +/** + * Dispose of this scrollbar. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Scrollbar.prototype.dispose = function() { + this.onMouseUpHandle_(); + Blockly.unbindEvent_(this.onMouseDownBarWrapper_); + this.onMouseDownBarWrapper_ = null; + Blockly.unbindEvent_(this.onMouseDownHandleWrapper_); + this.onMouseDownHandleWrapper_ = null; + + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + this.svgBackground_ = null; + this.svgHandle_ = null; + this.workspace_ = null; +}; + +/** + * Set the length of the scrollbar's handle and change the SVG attribute + * accordingly. + * @param {number} newLength The new scrollbar handle length. + */ +Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) { + this.handleLength_ = newLength; + this.svgHandle_.setAttribute(this.lengthAttribute_, this.handleLength_); +}; + +/** + * Set the offset of the scrollbar's handle and change the SVG attribute + * accordingly. + * @param {number} newPosition The new scrollbar handle offset. + */ +Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) { + this.handlePosition_ = newPosition; + this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_); +}; + +/** + * Set the size of the scrollbar's background and change the SVG attribute + * accordingly. + * @param {number} newSize The new scrollbar background length. + * @private + */ +Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) { + this.scrollViewSize_ = newSize; + this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_); +}; + +/** + * Set the position of the scrollbar's svg group. + * @param {number} x The new x coordinate. + * @param {number} y The new y coordinate. + */ +Blockly.Scrollbar.prototype.setPosition = function(x, y) { + this.position_.x = x; + this.position_.y = y; + + this.svgGroup_.setAttribute('transform', + 'translate(' + this.position_.x + ',' + this.position_.y + ')'); +}; + +/** + * Recalculate the scrollbar's location and its length. + * @param {Object=} opt_metrics A data structure of from the describing all the + * required dimensions. If not provided, it will be fetched from the host + * object. + */ +Blockly.Scrollbar.prototype.resize = function(opt_metrics) { + // Determine the location, height and width of the host element. + var hostMetrics = opt_metrics; + if (!hostMetrics) { + hostMetrics = this.workspace_.getMetrics(); + if (!hostMetrics) { + // Host element is likely not visible. + return; + } + } + + if (Blockly.Scrollbar.metricsAreEquivalent_(hostMetrics, + this.oldHostMetrics_)) { + return; + } + this.oldHostMetrics_ = hostMetrics; + + /* hostMetrics is an object with the following properties. + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the content, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .viewLeft: Offset of left edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .contentLeft: Offset of the left-most content from the x=0 coordinate, + * .absoluteTop: Top-edge of view. + * .absoluteLeft: Left-edge of view. + */ + if (this.horizontal_) { + this.resizeHorizontal_(hostMetrics); + } else { + this.resizeVertical_(hostMetrics); + } + // Resizing may have caused some scrolling. + this.onScroll_(); +}; + +/** + * Recalculate a horizontal scrollbar's location and length. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + * @private + */ +Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) { + // TODO: Inspect metrics to determine if we can get away with just a content + // resize. + this.resizeViewHorizontal(hostMetrics); +}; + +/** + * Recalculate a horizontal scrollbar's location on the screen and path length. + * This should be called when the layout or size of the window has changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) { + var viewSize = hostMetrics.viewWidth - 1; + if (this.pair_) { + // Shorten the scrollbar to make room for the corner square. + viewSize -= Blockly.Scrollbar.scrollbarThickness; + } + this.setScrollViewSize_(Math.max(0, viewSize)); + + var xCoordinate = hostMetrics.absoluteLeft + 0.5; + if (this.pair_ && this.workspace_.RTL) { + xCoordinate += Blockly.Scrollbar.scrollbarThickness; + } + + // Horizontal toolbar should always be just above the bottom of the workspace. + var yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight - + Blockly.Scrollbar.scrollbarThickness - 0.5; + this.setPosition(xCoordinate, yCoordinate); + + // If the view has been resized, a content resize will also be necessary. The + // reverse is not true. + this.resizeContentHorizontal(hostMetrics); +}; + +/** + * Recalculate a horizontal scrollbar's location within its path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) { + if (!this.pair_) { + // Only show the scrollbar if needed. + // Ideally this would also apply to scrollbar pairs, but that's a bigger + // headache (due to interactions with the corner square). + this.setVisible(this.scrollViewSize_ < hostMetrics.contentWidth); + } + + this.ratio_ = this.scrollViewSize_ / hostMetrics.contentWidth; + if (this.ratio_ == -Infinity || this.ratio_ == Infinity || + isNaN(this.ratio_)) { + this.ratio_ = 0; + } + + var handleLength = hostMetrics.viewWidth * this.ratio_; + this.setHandleLength_(Math.max(0, handleLength)); + + var handlePosition = (hostMetrics.viewLeft - hostMetrics.contentLeft) * + this.ratio_; + this.setHandlePosition(this.constrainHandle_(handlePosition)); +}; + +/** + * Recalculate a vertical scrollbar's location and length. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + * @private + */ +Blockly.Scrollbar.prototype.resizeVertical_ = function(hostMetrics) { + // TODO: Inspect metrics to determine if we can get away with just a content + // resize. + this.resizeViewVertical(hostMetrics); +}; + +/** + * Recalculate a vertical scrollbar's location on the screen and path length. + * This should be called when the layout or size of the window has changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) { + var viewSize = hostMetrics.viewHeight - 1; + if (this.pair_) { + // Shorten the scrollbar to make room for the corner square. + viewSize -= Blockly.Scrollbar.scrollbarThickness; + } + this.setScrollViewSize_(Math.max(0, viewSize)); + + var xCoordinate = hostMetrics.absoluteLeft + 0.5; + if (!this.workspace_.RTL) { + xCoordinate += hostMetrics.viewWidth - + Blockly.Scrollbar.scrollbarThickness - 1; + } + var yCoordinate = hostMetrics.absoluteTop + 0.5; + this.setPosition(xCoordinate, yCoordinate); + + // If the view has been resized, a content resize will also be necessary. The + // reverse is not true. + this.resizeContentVertical(hostMetrics); +}; + +/** + * Recalculate a vertical scrollbar's location within its path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) { + if (!this.pair_) { + // Only show the scrollbar if needed. + this.setVisible(this.scrollViewSize_ < hostMetrics.contentHeight); + } + + this.ratio_ = this.scrollViewSize_ / hostMetrics.contentHeight; + if (this.ratio_ == -Infinity || this.ratio_ == Infinity || + isNaN(this.ratio_)) { + this.ratio_ = 0; + } + + var handleLength = hostMetrics.viewHeight * this.ratio_; + this.setHandleLength_(Math.max(0, handleLength)); + + var handlePosition = (hostMetrics.viewTop - hostMetrics.contentTop) * + this.ratio_; + this.setHandlePosition(this.constrainHandle_(handlePosition)); +}; + +/** + * Create all the DOM elements required for a scrollbar. + * The resulting widget is not sized. + * @private + */ +Blockly.Scrollbar.prototype.createDom_ = function() { + /* Create the following DOM: + + + + + */ + var className = 'blocklyScrollbar' + + (this.horizontal_ ? 'Horizontal' : 'Vertical'); + this.svgGroup_ = Blockly.createSvgElement('g', {'class': className}, null); + this.svgBackground_ = Blockly.createSvgElement('rect', + {'class': 'blocklyScrollbarBackground'}, this.svgGroup_); + var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2); + this.svgHandle_ = Blockly.createSvgElement('rect', + {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius}, + this.svgGroup_); + Blockly.Scrollbar.insertAfter_(this.svgGroup_, + this.workspace_.getBubbleCanvas()); +}; + +/** + * Is the scrollbar visible. Non-paired scrollbars disappear when they aren't + * needed. + * @return {boolean} True if visible. + */ +Blockly.Scrollbar.prototype.isVisible = function() { + return this.isVisible_; +}; + +/** + * Set whether the scrollbar is visible. + * Only applies to non-paired scrollbars. + * @param {boolean} visible True if visible. + */ +Blockly.Scrollbar.prototype.setVisible = function(visible) { + if (visible == this.isVisible()) { + return; + } + // Ideally this would also apply to scrollbar pairs, but that's a bigger + // headache (due to interactions with the corner square). + if (this.pair_) { + throw 'Unable to toggle visibility of paired scrollbars.'; + } + + this.isVisible_ = visible; + + if (visible) { + this.svgGroup_.setAttribute('display', 'block'); + } else { + // Hide the scrollbar. + this.workspace_.setMetrics({x: 0, y: 0}); + this.svgGroup_.setAttribute('display', 'none'); + } +}; + +/** + * Scroll by one pageful. + * Called when scrollbar background is clicked. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) { + this.onMouseUpHandle_(); + if (Blockly.isRightButton(e)) { + // Right-click. + // Scrollbars have no context menu. + e.stopPropagation(); + return; + } + var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg(), + this.workspace_.getInverseScreenCTM()); + var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y; + + var handleXY = Blockly.getSvgXY_(this.svgHandle_, this.workspace_); + var handleStart = this.horizontal_ ? handleXY.x : handleXY.y; + var handlePosition = this.handlePosition_; + + var pageLength = this.handleLength_ * 0.95; + if (mouseLocation <= handleStart) { + // Decrease the scrollbar's value by a page. + handlePosition -= pageLength; + } else if (mouseLocation >= handleStart + this.handleLength_) { + // Increase the scrollbar's value by a page. + handlePosition += pageLength; + } + + this.setHandlePosition(this.constrainHandle_(handlePosition)); + + this.onScroll_(); + e.stopPropagation(); + e.preventDefault(); +}; + +/** + * Start a dragging operation. + * Called when scrollbar handle is clicked. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) { + this.onMouseUpHandle_(); + if (Blockly.isRightButton(e)) { + // Right-click. + // Scrollbars have no context menu. + e.stopPropagation(); + return; + } + // Look up the current translation and record it. + this.startDragHandle = this.handlePosition_; + // Record the current mouse position. + this.startDragMouse = this.horizontal_ ? e.clientX : e.clientY; + Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEvent_(document, + 'mouseup', this, this.onMouseUpHandle_); + Blockly.Scrollbar.onMouseMoveWrapper_ = Blockly.bindEvent_(document, + 'mousemove', this, this.onMouseMoveHandle_); + e.stopPropagation(); + e.preventDefault(); +}; + +/** + * Drag the scrollbar's handle. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) { + var currentMouse = this.horizontal_ ? e.clientX : e.clientY; + var mouseDelta = currentMouse - this.startDragMouse; + var handlePosition = this.startDragHandle + mouseDelta; + // Position the bar. + this.setHandlePosition(this.constrainHandle_(handlePosition)); + this.onScroll_(); +}; + +/** + * Stop binding to the global mouseup and mousemove events. + * @private + */ +Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() { + Blockly.hideChaff(true); + if (Blockly.Scrollbar.onMouseUpWrapper_) { + Blockly.unbindEvent_(Blockly.Scrollbar.onMouseUpWrapper_); + Blockly.Scrollbar.onMouseUpWrapper_ = null; + } + if (Blockly.Scrollbar.onMouseMoveWrapper_) { + Blockly.unbindEvent_(Blockly.Scrollbar.onMouseMoveWrapper_); + Blockly.Scrollbar.onMouseMoveWrapper_ = null; + } +}; + +/** + * Constrain the handle's position within the minimum (0) and maximum + * (length of scrollbar) values allowed for the scrollbar. + * @param {number} value Value that is potentially out of bounds. + * @return {number} Constrained value. + * @private + */ +Blockly.Scrollbar.prototype.constrainHandle_ = function(value) { + if (value <= 0 || isNaN(value) || this.scrollViewSize_ < this.handleLength_) { + value = 0; + } else { + value = Math.min(value, this.scrollViewSize_ - this.handleLength_); + } + return value; +}; + +/** + * Called when scrollbar is moved. + * @private + */ +Blockly.Scrollbar.prototype.onScroll_ = function() { + var ratio = this.handlePosition_ / this.scrollViewSize_; + if (isNaN(ratio)) { + ratio = 0; + } + var xyRatio = {}; + if (this.horizontal_) { + xyRatio.x = ratio; + } else { + xyRatio.y = ratio; + } + this.workspace_.setMetrics(xyRatio); +}; + +/** + * Set the scrollbar slider's position. + * @param {number} value The distance from the top/left end of the bar. + */ +Blockly.Scrollbar.prototype.set = function(value) { + this.setHandlePosition(this.constrainHandle_(value * this.ratio_)); + this.onScroll_(); +}; + +/** + * Insert a node after a reference node. + * Contrast with node.insertBefore function. + * @param {!Element} newNode New element to insert. + * @param {!Element} refNode Existing element to precede new node. + * @private + */ +Blockly.Scrollbar.insertAfter_ = function(newNode, refNode) { + var siblingNode = refNode.nextSibling; + var parentNode = refNode.parentNode; + if (!parentNode) { + throw 'Reference node has no parent.'; + } + if (siblingNode) { + parentNode.insertBefore(newNode, siblingNode); + } else { + parentNode.appendChild(newNode); + } +}; diff --git a/src/blockly/core/toolbox.js b/src/blockly/core/toolbox.js new file mode 100644 index 0000000..5a5096c --- /dev/null +++ b/src/blockly/core/toolbox.js @@ -0,0 +1,650 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Toolbox from whence to create blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Toolbox'); + +goog.require('Blockly.Flyout'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.math.Rect'); +goog.require('goog.style'); +goog.require('goog.ui.tree.TreeControl'); +goog.require('goog.ui.tree.TreeNode'); + + +/** + * Class for a Toolbox. + * Creates the toolbox's DOM. + * @param {!Blockly.Workspace} workspace The workspace in which to create new + * blocks. + * @constructor + */ +Blockly.Toolbox = function(workspace) { + /** + * @type {!Blockly.Workspace} + * @private + */ + this.workspace_ = workspace; + + /** + * Is RTL vs LTR. + * @type {boolean} + */ + this.RTL = workspace.options.RTL; + + /** + * Whether the toolbox should be laid out horizontally. + * @type {boolean} + * @private + */ + this.horizontalLayout_ = workspace.options.horizontalLayout; + + /** + * Position of the toolbox and flyout relative to the workspace. + * @type {number} + */ + this.toolboxPosition = workspace.options.toolboxPosition; + + /** + * Configuration constants for Closure's tree UI. + * @type {Object.} + * @private + */ + this.config_ = { + indentWidth: 19, + cssRoot: 'blocklyTreeRoot', + cssHideRoot: 'blocklyHidden', + cssItem: '', + cssTreeRow: 'blocklyTreeRow', + cssItemLabel: 'blocklyTreeLabel', + cssTreeIcon: 'blocklyTreeIcon', + cssExpandedFolderIcon: 'blocklyTreeIconOpen', + cssFileIcon: 'blocklyTreeIconNone', + cssSelectedRow: 'blocklyTreeSelected' + }; + + + /** + * Configuration constants for tree separator. + * @type {Object.} + * @private + */ + this.treeSeparatorConfig_ = { + cssTreeRow: 'blocklyTreeSeparator' + }; + + if (this.horizontalLayout_) { + this.config_['cssTreeRow'] = + this.config_['cssTreeRow'] + + (workspace.RTL ? + ' blocklyHorizontalTreeRtl' : ' blocklyHorizontalTree'); + + this.treeSeparatorConfig_['cssTreeRow'] = + 'blocklyTreeSeparatorHorizontal ' + + (workspace.RTL ? + 'blocklyHorizontalTreeRtl' : 'blocklyHorizontalTree'); + this.config_['cssTreeIcon'] = ''; + } +}; + +/** + * Width of the toolbox, which changes only in vertical layout. + * @type {number} + */ +Blockly.Toolbox.prototype.width = 0; + +/** + * Height of the toolbox, which changes only in horizontal layout. + * @type {number} + */ +Blockly.Toolbox.prototype.height = 0; + +/** + * The SVG group currently selected. + * @type {SVGGElement} + * @private + */ +Blockly.Toolbox.prototype.selectedOption_ = null; + +/** + * The tree node most recently selected. + * @type {goog.ui.tree.BaseNode} + * @private + */ +Blockly.Toolbox.prototype.lastCategory_ = null; + +/** + * Initializes the toolbox. + */ +Blockly.Toolbox.prototype.init = function() { + var workspace = this.workspace_; + var svg = this.workspace_.getParentSvg(); + + // Create an HTML container for the Toolbox menu. + this.HtmlDiv = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyToolboxDiv'); + this.HtmlDiv.setAttribute('dir', workspace.RTL ? 'RTL' : 'LTR'); + svg.parentNode.insertBefore(this.HtmlDiv, svg); + + // Clicking on toolbox closes popups. + Blockly.bindEvent_(this.HtmlDiv, 'mousedown', this, + function(e) { + if (Blockly.isRightButton(e) || e.target == this.HtmlDiv) { + // Close flyout. + Blockly.hideChaff(false); + } else { + // Just close popups. + Blockly.hideChaff(true); + } + }); + var workspaceOptions = { + disabledPatternId: workspace.options.disabledPatternId, + parentWorkspace: workspace, + RTL: workspace.RTL, + horizontalLayout: workspace.horizontalLayout, + toolboxPosition: workspace.options.toolboxPosition + }; + /** + * @type {!Blockly.Flyout} + * @private + */ + this.flyout_ = new Blockly.Flyout(workspaceOptions); + goog.dom.insertSiblingAfter(this.flyout_.createDom(), workspace.svgGroup_); + this.flyout_.init(workspace); + + this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif'; + this.config_['cssCollapsedFolderIcon'] = + 'blocklyTreeIconClosed' + (workspace.RTL ? 'Rtl' : 'Ltr'); + var tree = new Blockly.Toolbox.TreeControl(this, this.config_); + this.tree_ = tree; + tree.setShowRootNode(false); + tree.setShowLines(false); + tree.setShowExpandIcons(false); + tree.setSelectedItem(null); + var openNode = this.populate_(workspace.options.languageTree); + tree.render(this.HtmlDiv); + if (openNode) { + tree.setSelectedItem(openNode); + } + this.addColour_(); + this.position(); +}; + +/** + * Dispose of this toolbox. + */ +Blockly.Toolbox.prototype.dispose = function() { + this.flyout_.dispose(); + this.tree_.dispose(); + goog.dom.removeNode(this.HtmlDiv); + this.workspace_ = null; + this.lastCategory_ = null; +}; + +/** + * Get the width of the toolbox. + * @return {number} The width of the toolbox. + */ +Blockly.Toolbox.prototype.getWidth = function() { + return this.width; +}; + +/** + * Get the height of the toolbox. + * @return {number} The width of the toolbox. + */ +Blockly.Toolbox.prototype.getHeight = function() { + return this.height; +}; + +/** + * Move the toolbox to the edge. + */ +Blockly.Toolbox.prototype.position = function() { + var treeDiv = this.HtmlDiv; + if (!treeDiv) { + // Not initialized yet. + return; + } + var svg = this.workspace_.getParentSvg(); + var svgPosition = goog.style.getPageOffset(svg); + var svgSize = Blockly.svgSize(svg); + if (this.horizontalLayout_) { + treeDiv.style.left = '0'; + treeDiv.style.height = 'auto'; + treeDiv.style.width = svgSize.width + 'px'; + this.height = treeDiv.offsetHeight; + if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { // Top + treeDiv.style.top = '0'; + } else { // Bottom + treeDiv.style.bottom = '0'; + } + } else { + if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { // Right + treeDiv.style.right = '0'; + } else { // Left + treeDiv.style.left = '0'; + } + treeDiv.style.height = svgSize.height + 'px'; + this.width = treeDiv.offsetWidth; + } + this.flyout_.position(); +}; + +/** + * Fill the toolbox with categories and blocks. + * @param {!Node} newTree DOM tree of blocks. + * @return {Node} Tree node to open at startup (or null). + * @private + */ +Blockly.Toolbox.prototype.populate_ = function(newTree) { + this.tree_.removeChildren(); // Delete any existing content. + this.tree_.blocks = []; + this.hasColours_ = false; + var openNode = + this.syncTrees_(newTree, this.tree_, this.workspace_.options.pathToMedia); + + if (this.tree_.blocks.length) { + throw 'Toolbox cannot have both blocks and categories in the root level.'; + } + + // Fire a resize event since the toolbox may have changed width and height. + this.workspace_.resizeContents(); + return openNode; +}; + +/** + * Sync trees of the toolbox. + * @param {!Node} treeIn DOM tree of blocks. + * @param {!Blockly.Toolbox.TreeControl} treeOut + * @param {string} pathToMedia + * @return {Node} Tree node to open at startup (or null). + * @private + */ +Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) { + var openNode = null; + var lastElement = null; + for (var i = 0, childIn; childIn = treeIn.childNodes[i]; i++) { + if (!childIn.tagName) { + // Skip over text. + continue; + } + switch (childIn.tagName.toUpperCase()) { + case 'CATEGORY': + var childOut = this.tree_.createNode(childIn.getAttribute('name')); + childOut.blocks = []; + treeOut.add(childOut); + var custom = childIn.getAttribute('custom'); + if (custom) { + // Variables and procedures are special dynamic categories. + childOut.blocks = custom; + } else { + var newOpenNode = this.syncTrees_(childIn, childOut, pathToMedia); + if (newOpenNode) { + openNode = newOpenNode; + } + } + var colour = childIn.getAttribute('colour'); + if (goog.isString(colour)) { + if (colour.match(/^#[0-9a-fA-F]{6}$/)) { + childOut.hexColour = colour; + } else { + childOut.hexColour = Blockly.hueToRgb(colour); + } + this.hasColours_ = true; + } else { + childOut.hexColour = ''; + } + if (childIn.getAttribute('expanded') == 'true') { + if (childOut.blocks.length) { + // This is a category that directly contians blocks. + // After the tree is rendered, open this category and show flyout. + openNode = childOut; + } + childOut.setExpanded(true); + } else { + childOut.setExpanded(false); + } + lastElement = childIn; + break; + case 'SEP': + if (lastElement) { + if (lastElement.tagName.toUpperCase() == 'CATEGORY') { + // Separator between two categories. + // + treeOut.add(new Blockly.Toolbox.TreeSeparator( + this.treeSeparatorConfig_)); + } else { + // Change the gap between two blocks. + // + // The default gap is 24, can be set larger or smaller. + // Note that a deprecated method is to add a gap to a block. + // + var newGap = parseFloat(childIn.getAttribute('gap')); + if (!isNaN(newGap) && lastElement) { + lastElement.setAttribute('gap', newGap); + } + } + } + break; + case 'BLOCK': + case 'SHADOW': + treeOut.blocks.push(childIn); + lastElement = childIn; + break; + } + } + return openNode; +}; + +/** + * Recursively add colours to this toolbox. + * @param {Blockly.Toolbox.TreeNode} opt_tree Starting point of tree. + * Defaults to the root node. + * @private + */ +Blockly.Toolbox.prototype.addColour_ = function(opt_tree) { + var tree = opt_tree || this.tree_; + var children = tree.getChildren(); + for (var i = 0, child; child = children[i]; i++) { + var element = child.getRowElement(); + if (element) { + if (this.hasColours_) { + var border = '8px solid ' + (child.hexColour || '#ddd'); + } else { + var border = 'none'; + } + if (this.workspace_.RTL) { + element.style.borderRight = border; + } else { + element.style.borderLeft = border; + } + } + this.addColour_(child); + } +}; + +/** + * Unhighlight any previously specified option. + */ +Blockly.Toolbox.prototype.clearSelection = function() { + this.tree_.setSelectedItem(null); +}; + +/** + * Return the deletion rectangle for this toolbox. + * @return {goog.math.Rect} Rectangle in which to delete. + */ +Blockly.Toolbox.prototype.getClientRect = function() { + if (!this.HtmlDiv) { + return null; + } + + // BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox + // area are still deleted. Must be smaller than Infinity, but larger than + // the largest screen size. + var BIG_NUM = 10000000; + var toolboxRect = this.HtmlDiv.getBoundingClientRect(); + + var x = toolboxRect.left; + var y = toolboxRect.top; + var width = toolboxRect.width; + var height = toolboxRect.height; + + // Assumes that the toolbox is on the SVG edge. If this changes + // (e.g. toolboxes in mutators) then this code will need to be more complex. + if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { + return new goog.math.Rect(-BIG_NUM, -BIG_NUM, BIG_NUM + x + width, + 2 * BIG_NUM); + } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, 2 * BIG_NUM); + } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { + return new goog.math.Rect(-BIG_NUM, -BIG_NUM, 2 * BIG_NUM, + BIG_NUM + y + height); + } else { // Bottom + return new goog.math.Rect(0, y, 2 * BIG_NUM, BIG_NUM + width); + } +}; + +/** + * Update the flyout's contents without closing it. Should be used in response + * to a change in one of the dynamic categories, such as variables or + * procedures. + */ +Blockly.Toolbox.prototype.refreshSelection = function() { + var selectedItem = this.tree_.getSelectedItem(); + if (selectedItem && selectedItem.blocks) { + this.flyout_.show(selectedItem.blocks); + } +}; + +// Extending Closure's Tree UI. + +/** + * Extention of a TreeControl object that uses a custom tree node. + * @param {Blockly.Toolbox} toolbox The parent toolbox for this tree. + * @param {Object} config The configuration for the tree. See + * goog.ui.tree.TreeControl.DefaultConfig. + * @constructor + * @extends {goog.ui.tree.TreeControl} + */ +Blockly.Toolbox.TreeControl = function(toolbox, config) { + this.toolbox_ = toolbox; + goog.ui.tree.TreeControl.call(this, goog.html.SafeHtml.EMPTY, config); +}; +goog.inherits(Blockly.Toolbox.TreeControl, goog.ui.tree.TreeControl); + +/** + * Adds touch handling to TreeControl. + * @override + */ +Blockly.Toolbox.TreeControl.prototype.enterDocument = function() { + Blockly.Toolbox.TreeControl.superClass_.enterDocument.call(this); + + // Add touch handler. + if (goog.events.BrowserFeature.TOUCH_ENABLED) { + var el = this.getElement(); + Blockly.bindEvent_(el, goog.events.EventType.TOUCHSTART, this, + this.handleTouchEvent_); + } +}; + +/** + * Handles touch events. + * @param {!goog.events.BrowserEvent} e The browser event. + * @private + */ +Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_ = function(e) { + e.preventDefault(); + var node = this.getNodeFromEvent_(e); + if (node && e.type === goog.events.EventType.TOUCHSTART) { + // Fire asynchronously since onMouseDown takes long enough that the browser + // would fire the default mouse event before this method returns. + setTimeout(function() { + node.onMouseDown(e); // Same behaviour for click and touch. + }, 1); + } +}; + +/** + * Creates a new tree node using a custom tree node. + * @param {string=} opt_html The HTML content of the node label. + * @return {!goog.ui.tree.TreeNode} The new item. + * @override + */ +Blockly.Toolbox.TreeControl.prototype.createNode = function(opt_html) { + return new Blockly.Toolbox.TreeNode(this.toolbox_, opt_html ? + goog.html.SafeHtml.htmlEscape(opt_html) : goog.html.SafeHtml.EMPTY, + this.getConfig(), this.getDomHelper()); +}; + +/** + * Display/hide the flyout when an item is selected. + * @param {goog.ui.tree.BaseNode} node The item to select. + * @override + */ +Blockly.Toolbox.TreeControl.prototype.setSelectedItem = function(node) { + var toolbox = this.toolbox_; + if (node == this.selectedItem_ || node == toolbox.tree_) { + return; + } + if (toolbox.lastCategory_) { + toolbox.lastCategory_.getRowElement().style.backgroundColor = ''; + } + if (node) { + var hexColour = node.hexColour || '#57e'; + node.getRowElement().style.backgroundColor = hexColour; + // Add colours to child nodes which may have been collapsed and thus + // not rendered. + toolbox.addColour_(node); + } + var oldNode = this.getSelectedItem(); + goog.ui.tree.TreeControl.prototype.setSelectedItem.call(this, node); + if (node && node.blocks && node.blocks.length) { + toolbox.flyout_.show(node.blocks); + // Scroll the flyout to the top if the category has changed. + if (toolbox.lastCategory_ != node) { + toolbox.flyout_.scrollToStart(); + } + } else { + // Hide the flyout. + toolbox.flyout_.hide(); + } + if (oldNode != node && oldNode != this) { + var event = new Blockly.Events.Ui(null, 'category', + oldNode && oldNode.getHtml(), node && node.getHtml()); + event.workspaceId = toolbox.workspace_.id; + Blockly.Events.fire(event); + } + if (node) { + toolbox.lastCategory_ = node; + } +}; + +/** + * A single node in the tree, customized for Blockly's UI. + * @param {Blockly.Toolbox} toolbox The parent toolbox for this tree. + * @param {!goog.html.SafeHtml} html The HTML content of the node label. + * @param {Object=} opt_config The configuration for the tree. See + * goog.ui.tree.TreeControl.DefaultConfig. If not specified, a default config + * will be used. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + * @constructor + * @extends {goog.ui.tree.TreeNode} + */ +Blockly.Toolbox.TreeNode = function(toolbox, html, opt_config, opt_domHelper) { + goog.ui.tree.TreeNode.call(this, html, opt_config, opt_domHelper); + if (toolbox) { + this.horizontalLayout_ = toolbox.horizontalLayout_; + var resize = function() { + // Even though the div hasn't changed size, the visible workspace + // surface of the workspace has, so we may need to reposition everything. + Blockly.svgResize(toolbox.workspace_); + }; + // Fire a resize event since the toolbox may have changed width. + goog.events.listen(toolbox.tree_, + goog.ui.tree.BaseNode.EventType.EXPAND, resize); + goog.events.listen(toolbox.tree_, + goog.ui.tree.BaseNode.EventType.COLLAPSE, resize); + } +}; +goog.inherits(Blockly.Toolbox.TreeNode, goog.ui.tree.TreeNode); + +/** + * Supress population of the +/- icon. + * @return {!goog.html.SafeHtml} The source for the icon. + * @override + */ +Blockly.Toolbox.TreeNode.prototype.getExpandIconSafeHtml = function() { + return goog.html.SafeHtml.create('span'); +}; + +/** + * Expand or collapse the node on mouse click. + * @param {!goog.events.BrowserEvent} e The browser event. + * @override + */ +Blockly.Toolbox.TreeNode.prototype.onMouseDown = function(e) { + // Expand icon. + if (this.hasChildren() && this.isUserCollapsible_) { + this.toggle(); + this.select(); + } else if (this.isSelected()) { + this.getTree().setSelectedItem(null); + } else { + this.select(); + } + this.updateRow(); +}; + +/** + * Supress the inherited double-click behaviour. + * @param {!goog.events.BrowserEvent} e The browser event. + * @override + * @private + */ +Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function(e) { + // NOP. +}; + +/** + * Remap event.keyCode in horizontalLayout so that arrow + * keys work properly and call original onKeyDown handler. + * @param {!goog.events.BrowserEvent} e The browser event. + * @return {boolean} The handled value. + * @override + * @private + */ +Blockly.Toolbox.TreeNode.prototype.onKeyDown = function(e) { + if (this.horizontalLayout_) { + var map = {}; + map[goog.events.KeyCodes.RIGHT] = goog.events.KeyCodes.DOWN; + map[goog.events.KeyCodes.LEFT] = goog.events.KeyCodes.UP; + map[goog.events.KeyCodes.UP] = goog.events.KeyCodes.LEFT; + map[goog.events.KeyCodes.DOWN] = goog.events.KeyCodes.RIGHT; + + var newKeyCode = map[e.keyCode]; + e.keyCode = newKeyCode || e.keyCode; + } + return Blockly.Toolbox.TreeNode.superClass_.onKeyDown.call(this, e); +}; + +/** + * A blank separator node in the tree. + * @param {Object=} config The configuration for the tree. See + * goog.ui.tree.TreeControl.DefaultConfig. If not specified, a default config + * will be used. + * @constructor + * @extends {Blockly.Toolbox.TreeNode} + */ +Blockly.Toolbox.TreeSeparator = function(config) { + Blockly.Toolbox.TreeNode.call(this, null, '', config); +}; +goog.inherits(Blockly.Toolbox.TreeSeparator, Blockly.Toolbox.TreeNode); diff --git a/src/blockly/core/tooltip.js b/src/blockly/core/tooltip.js new file mode 100644 index 0000000..2ff612b --- /dev/null +++ b/src/blockly/core/tooltip.js @@ -0,0 +1,286 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Library to create tooltips for Blockly. + * First, call Blockly.Tooltip.init() after onload. + * Second, set the 'tooltip' property on any SVG element that needs a tooltip. + * If the tooltip is a string, then that message will be displayed. + * If the tooltip is an SVG element, then that object's tooltip will be used. + * Third, call Blockly.Tooltip.bindMouseEvents(e) passing the SVG element. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Tooltip'); + +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); + + +/** + * Is a tooltip currently showing? + */ +Blockly.Tooltip.visible = false; + +/** + * Maximum width (in characters) of a tooltip. + */ +Blockly.Tooltip.LIMIT = 50; + +/** + * PID of suspended thread to clear tooltip on mouse out. + * @private + */ +Blockly.Tooltip.mouseOutPid_ = 0; + +/** + * PID of suspended thread to show the tooltip. + * @private + */ +Blockly.Tooltip.showPid_ = 0; + +/** + * Last observed X location of the mouse pointer (freezes when tooltip appears). + * @private + */ +Blockly.Tooltip.lastX_ = 0; + +/** + * Last observed Y location of the mouse pointer (freezes when tooltip appears). + * @private + */ +Blockly.Tooltip.lastY_ = 0; + +/** + * Current element being pointed at. + * @private + */ +Blockly.Tooltip.element_ = null; + +/** + * Once a tooltip has opened for an element, that element is 'poisoned' and + * cannot respawn a tooltip until the pointer moves over a different element. + * @private + */ +Blockly.Tooltip.poisonedElement_ = null; + +/** + * Horizontal offset between mouse cursor and tooltip. + */ +Blockly.Tooltip.OFFSET_X = 0; + +/** + * Vertical offset between mouse cursor and tooltip. + */ +Blockly.Tooltip.OFFSET_Y = 10; + +/** + * Radius mouse can move before killing tooltip. + */ +Blockly.Tooltip.RADIUS_OK = 10; + +/** + * Delay before tooltip appears. + */ +Blockly.Tooltip.HOVER_MS = 750; + +/** + * Horizontal padding between tooltip and screen edge. + */ +Blockly.Tooltip.MARGINS = 5; + +/** + * The HTML container. Set once by Blockly.Tooltip.createDom. + * @type {Element} + */ +Blockly.Tooltip.DIV = null; + +/** + * Create the tooltip div and inject it onto the page. + */ +Blockly.Tooltip.createDom = function() { + if (Blockly.Tooltip.DIV) { + return; // Already created. + } + // Create an HTML container for popup overlays (e.g. editor widgets). + Blockly.Tooltip.DIV = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyTooltipDiv'); + document.body.appendChild(Blockly.Tooltip.DIV); +}; + +/** + * Binds the required mouse events onto an SVG element. + * @param {!Element} element SVG element onto which tooltip is to be bound. + */ +Blockly.Tooltip.bindMouseEvents = function(element) { + Blockly.bindEvent_(element, 'mouseover', null, Blockly.Tooltip.onMouseOver_); + Blockly.bindEvent_(element, 'mouseout', null, Blockly.Tooltip.onMouseOut_); + Blockly.bindEvent_(element, 'mousemove', null, Blockly.Tooltip.onMouseMove_); +}; + +/** + * Hide the tooltip if the mouse is over a different object. + * Initialize the tooltip to potentially appear for this object. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.Tooltip.onMouseOver_ = function(e) { + // If the tooltip is an object, treat it as a pointer to the next object in + // the chain to look at. Terminate when a string or function is found. + var element = e.target; + while (!goog.isString(element.tooltip) && !goog.isFunction(element.tooltip)) { + element = element.tooltip; + } + if (Blockly.Tooltip.element_ != element) { + Blockly.Tooltip.hide(); + Blockly.Tooltip.poisonedElement_ = null; + Blockly.Tooltip.element_ = element; + } + // Forget about any immediately preceeding mouseOut event. + clearTimeout(Blockly.Tooltip.mouseOutPid_); +}; + +/** + * Hide the tooltip if the mouse leaves the object and enters the workspace. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.Tooltip.onMouseOut_ = function(e) { + // Moving from one element to another (overlapping or with no gap) generates + // a mouseOut followed instantly by a mouseOver. Fork off the mouseOut + // event and kill it if a mouseOver is received immediately. + // This way the task only fully executes if mousing into the void. + Blockly.Tooltip.mouseOutPid_ = setTimeout(function() { + Blockly.Tooltip.element_ = null; + Blockly.Tooltip.poisonedElement_ = null; + Blockly.Tooltip.hide(); + }, 1); + clearTimeout(Blockly.Tooltip.showPid_); +}; + +/** + * When hovering over an element, schedule a tooltip to be shown. If a tooltip + * is already visible, hide it if the mouse strays out of a certain radius. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.Tooltip.onMouseMove_ = function(e) { + if (!Blockly.Tooltip.element_ || !Blockly.Tooltip.element_.tooltip) { + // No tooltip here to show. + return; + } else if (Blockly.dragMode_ != Blockly.DRAG_NONE) { + // Don't display a tooltip during a drag. + return; + } else if (Blockly.WidgetDiv.isVisible()) { + // Don't display a tooltip if a widget is open (tooltip would be under it). + return; + } + if (Blockly.Tooltip.visible) { + // Compute the distance between the mouse position when the tooltip was + // shown and the current mouse position. Pythagorean theorem. + var dx = Blockly.Tooltip.lastX_ - e.pageX; + var dy = Blockly.Tooltip.lastY_ - e.pageY; + if (Math.sqrt(dx * dx + dy * dy) > Blockly.Tooltip.RADIUS_OK) { + Blockly.Tooltip.hide(); + } + } else if (Blockly.Tooltip.poisonedElement_ != Blockly.Tooltip.element_) { + // The mouse moved, clear any previously scheduled tooltip. + clearTimeout(Blockly.Tooltip.showPid_); + // Maybe this time the mouse will stay put. Schedule showing of tooltip. + Blockly.Tooltip.lastX_ = e.pageX; + Blockly.Tooltip.lastY_ = e.pageY; + Blockly.Tooltip.showPid_ = + setTimeout(Blockly.Tooltip.show_, Blockly.Tooltip.HOVER_MS); + } +}; + +/** + * Hide the tooltip. + */ +Blockly.Tooltip.hide = function() { + if (Blockly.Tooltip.visible) { + Blockly.Tooltip.visible = false; + if (Blockly.Tooltip.DIV) { + Blockly.Tooltip.DIV.style.display = 'none'; + } + } + clearTimeout(Blockly.Tooltip.showPid_); +}; + +/** + * Create the tooltip and show it. + * @private + */ +Blockly.Tooltip.show_ = function() { + Blockly.Tooltip.poisonedElement_ = Blockly.Tooltip.element_; + if (!Blockly.Tooltip.DIV) { + return; + } + // Erase all existing text. + goog.dom.removeChildren(/** @type {!Element} */ (Blockly.Tooltip.DIV)); + // Get the new text. + var tip = Blockly.Tooltip.element_.tooltip; + while (goog.isFunction(tip)) { + tip = tip(); + } + tip = Blockly.utils.wrap(tip, Blockly.Tooltip.LIMIT); + // Create new text, line by line. + var lines = tip.split('\n'); + for (var i = 0; i < lines.length; i++) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(lines[i])); + Blockly.Tooltip.DIV.appendChild(div); + } + var rtl = Blockly.Tooltip.element_.RTL; + var windowSize = goog.dom.getViewportSize(); + // Display the tooltip. + Blockly.Tooltip.DIV.style.direction = rtl ? 'rtl' : 'ltr'; + Blockly.Tooltip.DIV.style.display = 'block'; + Blockly.Tooltip.visible = true; + // Move the tooltip to just below the cursor. + var anchorX = Blockly.Tooltip.lastX_; + if (rtl) { + anchorX -= Blockly.Tooltip.OFFSET_X + Blockly.Tooltip.DIV.offsetWidth; + } else { + anchorX += Blockly.Tooltip.OFFSET_X; + } + var anchorY = Blockly.Tooltip.lastY_ + Blockly.Tooltip.OFFSET_Y; + + if (anchorY + Blockly.Tooltip.DIV.offsetHeight > + windowSize.height + window.scrollY) { + // Falling off the bottom of the screen; shift the tooltip up. + anchorY -= Blockly.Tooltip.DIV.offsetHeight + 2 * Blockly.Tooltip.OFFSET_Y; + } + if (rtl) { + // Prevent falling off left edge in RTL mode. + anchorX = Math.max(Blockly.Tooltip.MARGINS - window.scrollX, anchorX); + } else { + if (anchorX + Blockly.Tooltip.DIV.offsetWidth > + windowSize.width + window.scrollX - 2 * Blockly.Tooltip.MARGINS) { + // Falling off the right edge of the screen; + // clamp the tooltip on the edge. + anchorX = windowSize.width - Blockly.Tooltip.DIV.offsetWidth - + 2 * Blockly.Tooltip.MARGINS; + } + } + Blockly.Tooltip.DIV.style.top = anchorY + 'px'; + Blockly.Tooltip.DIV.style.left = anchorX + 'px'; +}; diff --git a/src/blockly/core/trashcan.js b/src/blockly/core/trashcan.js new file mode 100644 index 0000000..28baa0f --- /dev/null +++ b/src/blockly/core/trashcan.js @@ -0,0 +1,332 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing a trash can icon. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Trashcan'); + +goog.require('goog.Timer'); +goog.require('goog.dom'); +goog.require('goog.math'); +goog.require('goog.math.Rect'); + + +/** + * Class for a trash can. + * @param {!Blockly.Workspace} workspace The workspace to sit in. + * @constructor + */ +Blockly.Trashcan = function(workspace) { + this.workspace_ = workspace; +}; + +/** + * Width of both the trash can and lid images. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.WIDTH_ = 47; + +/** + * Height of the trashcan image (minus lid). + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.BODY_HEIGHT_ = 44; + +/** + * Height of the lid image. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.LID_HEIGHT_ = 16; + +/** + * Distance between trashcan and bottom edge of workspace. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.MARGIN_BOTTOM_ = 20; + +/** + * Distance between trashcan and right edge of workspace. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.MARGIN_SIDE_ = 20; + +/** + * Extent of hotspot on all sides beyond the size of the image. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.MARGIN_HOTSPOT_ = 10; + +/** + * Location of trashcan in sprite image. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.SPRITE_LEFT_ = 0; + +/** + * Location of trashcan in sprite image. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.SPRITE_TOP_ = 32; + +/** + * Current open/close state of the lid. + * @type {boolean} + */ +Blockly.Trashcan.prototype.isOpen = false; + +/** + * The SVG group containing the trash can. + * @type {Element} + * @private + */ +Blockly.Trashcan.prototype.svgGroup_ = null; + +/** + * The SVG image element of the trash can lid. + * @type {Element} + * @private + */ +Blockly.Trashcan.prototype.svgLid_ = null; + +/** + * Task ID of opening/closing animation. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.lidTask_ = 0; + +/** + * Current state of lid opening (0.0 = closed, 1.0 = open). + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.lidOpen_ = 0; + +/** + * Left coordinate of the trash can. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.left_ = 0; + +/** + * Top coordinate of the trash can. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.top_ = 0; + +/** + * Create the trash can elements. + * @return {!Element} The trash can's SVG group. + */ +Blockly.Trashcan.prototype.createDom = function() { + /* Here's the markup that will be generated: + + + + + + + + + + + */ + this.svgGroup_ = Blockly.createSvgElement('g', + {'class': 'blocklyTrash'}, null); + var rnd = String(Math.random()).substring(2); + var clip = Blockly.createSvgElement('clipPath', + {'id': 'blocklyTrashBodyClipPath' + rnd}, + this.svgGroup_); + Blockly.createSvgElement('rect', + {'width': this.WIDTH_, 'height': this.BODY_HEIGHT_, + 'y': this.LID_HEIGHT_}, + clip); + var body = Blockly.createSvgElement('image', + {'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_, + 'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_, + 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')'}, + this.svgGroup_); + body.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + this.workspace_.options.pathToMedia + Blockly.SPRITE.url); + + var clip = Blockly.createSvgElement('clipPath', + {'id': 'blocklyTrashLidClipPath' + rnd}, + this.svgGroup_); + Blockly.createSvgElement('rect', + {'width': this.WIDTH_, 'height': this.LID_HEIGHT_}, clip); + this.svgLid_ = Blockly.createSvgElement('image', + {'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_, + 'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_, + 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')'}, + this.svgGroup_); + this.svgLid_.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + this.workspace_.options.pathToMedia + Blockly.SPRITE.url); + + Blockly.bindEvent_(this.svgGroup_, 'mouseup', this, this.click); + this.animateLid_(); + return this.svgGroup_; +}; + +/** + * Initialize the trash can. + * @param {number} bottom Distance from workspace bottom to bottom of trashcan. + * @return {number} Distance from workspace bottom to the top of trashcan. + */ +Blockly.Trashcan.prototype.init = function(bottom) { + this.bottom_ = this.MARGIN_BOTTOM_ + bottom; + this.setOpen_(false); + return this.bottom_ + this.BODY_HEIGHT_ + this.LID_HEIGHT_; +}; + +/** + * Dispose of this trash can. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Trashcan.prototype.dispose = function() { + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.svgLid_ = null; + this.workspace_ = null; + goog.Timer.clear(this.lidTask_); +}; + +/** + * Move the trash can to the bottom-right corner. + */ +Blockly.Trashcan.prototype.position = function() { + var metrics = this.workspace_.getMetrics(); + if (!metrics) { + // There are no metrics available (workspace is probably not visible). + return; + } + if (this.workspace_.RTL) { + this.left_ = this.MARGIN_SIDE_ + Blockly.Scrollbar.scrollbarThickness; + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { + this.left_ += metrics.flyoutWidth; + if (this.workspace_.toolbox_) { + this.left_ += metrics.absoluteLeft; + } + } + } else { + this.left_ = metrics.viewWidth + metrics.absoluteLeft - + this.WIDTH_ - this.MARGIN_SIDE_ - Blockly.Scrollbar.scrollbarThickness; + + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + this.left_ -= metrics.flyoutWidth; + } + } + this.top_ = metrics.viewHeight + metrics.absoluteTop - + (this.BODY_HEIGHT_ + this.LID_HEIGHT_) - this.bottom_; + + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { + this.top_ -= metrics.flyoutHeight; + } + this.svgGroup_.setAttribute('transform', + 'translate(' + this.left_ + ',' + this.top_ + ')'); +}; + +/** + * Return the deletion rectangle for this trash can. + * @return {goog.math.Rect} Rectangle in which to delete. + */ +Blockly.Trashcan.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + + var trashRect = this.svgGroup_.getBoundingClientRect(); + var left = trashRect.left + this.SPRITE_LEFT_ - this.MARGIN_HOTSPOT_; + var top = trashRect.top + this.SPRITE_TOP_ - this.MARGIN_HOTSPOT_; + var width = this.WIDTH_ + 2 * this.MARGIN_HOTSPOT_; + var height = this.LID_HEIGHT_ + this.BODY_HEIGHT_ + 2 * this.MARGIN_HOTSPOT_; + return new goog.math.Rect(left, top, width, height); + +}; + +/** + * Flip the lid open or shut. + * @param {boolean} state True if open. + * @private + */ +Blockly.Trashcan.prototype.setOpen_ = function(state) { + if (this.isOpen == state) { + return; + } + goog.Timer.clear(this.lidTask_); + this.isOpen = state; + this.animateLid_(); +}; + +/** + * Rotate the lid open or closed by one step. Then wait and recurse. + * @private + */ +Blockly.Trashcan.prototype.animateLid_ = function() { + this.lidOpen_ += this.isOpen ? 0.2 : -0.2; + this.lidOpen_ = goog.math.clamp(this.lidOpen_, 0, 1); + var lidAngle = this.lidOpen_ * 45; + this.svgLid_.setAttribute('transform', 'rotate(' + + (this.workspace_.RTL ? -lidAngle : lidAngle) + ',' + + (this.workspace_.RTL ? 4 : this.WIDTH_ - 4) + ',' + + (this.LID_HEIGHT_ - 2) + ')'); + var opacity = goog.math.lerp(0.4, 0.8, this.lidOpen_); + this.svgGroup_.style.opacity = opacity; + if (this.lidOpen_ > 0 && this.lidOpen_ < 1) { + this.lidTask_ = goog.Timer.callOnce(this.animateLid_, 20, this); + } +}; + +/** + * Flip the lid shut. + * Called externally after a drag. + */ +Blockly.Trashcan.prototype.close = function() { + this.setOpen_(false); +}; + +/** + * Inspect the contents of the trash. + */ +Blockly.Trashcan.prototype.click = function() { + var dx = this.workspace_.startScrollX - this.workspace_.scrollX; + var dy = this.workspace_.startScrollY - this.workspace_.scrollY; + if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) { + return; + } + console.log('TODO: Inspect trash.'); +}; diff --git a/src/blockly/core/utils.js b/src/blockly/core/utils.js new file mode 100644 index 0000000..fe13572 --- /dev/null +++ b/src/blockly/core/utils.js @@ -0,0 +1,668 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Utility methods. + * These methods are not specific to Blockly, and could be factored out into + * a JavaScript framework such as Closure. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.utils'); + +goog.require('goog.dom'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.math.Coordinate'); +goog.require('goog.userAgent'); + + +/** + * Add a CSS class to a element. + * Similar to Closure's goog.dom.classes.add, except it handles SVG elements. + * @param {!Element} element DOM element to add class to. + * @param {string} className Name of class to add. + * @private + */ +Blockly.addClass_ = function(element, className) { + var classes = element.getAttribute('class') || ''; + if ((' ' + classes + ' ').indexOf(' ' + className + ' ') == -1) { + if (classes) { + classes += ' '; + } + element.setAttribute('class', classes + className); + } +}; + +/** + * Remove a CSS class from a element. + * Similar to Closure's goog.dom.classes.remove, except it handles SVG elements. + * @param {!Element} element DOM element to remove class from. + * @param {string} className Name of class to remove. + * @private + */ +Blockly.removeClass_ = function(element, className) { + var classes = element.getAttribute('class'); + if ((' ' + classes + ' ').indexOf(' ' + className + ' ') != -1) { + var classList = classes.split(/\s+/); + for (var i = 0; i < classList.length; i++) { + if (!classList[i] || classList[i] == className) { + classList.splice(i, 1); + i--; + } + } + if (classList.length) { + element.setAttribute('class', classList.join(' ')); + } else { + element.removeAttribute('class'); + } + } +}; + +/** + * Checks if an element has the specified CSS class. + * Similar to Closure's goog.dom.classes.has, except it handles SVG elements. + * @param {!Element} element DOM element to check. + * @param {string} className Name of class to check. + * @return {boolean} True if class exists, false otherwise. + * @private + */ +Blockly.hasClass_ = function(element, className) { + var classes = element.getAttribute('class'); + return (' ' + classes + ' ').indexOf(' ' + className + ' ') != -1; +}; + +/** + * Bind an event to a function call. + * @param {!Node} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @return {!Array.} Opaque data that can be passed to unbindEvent_. + * @private + */ +Blockly.bindEvent_ = function(node, name, thisObject, func) { + if (thisObject) { + var wrapFunc = function(e) { + func.call(thisObject, e); + }; + } else { + var wrapFunc = func; + } + node.addEventListener(name, wrapFunc, false); + var bindData = [[node, name, wrapFunc]]; + // Add equivalent touch event. + if (name in Blockly.bindEvent_.TOUCH_MAP) { + wrapFunc = function(e) { + // Punt on multitouch events. + if (e.changedTouches.length == 1) { + // Map the touch event's properties to the event. + var touchPoint = e.changedTouches[0]; + e.clientX = touchPoint.clientX; + e.clientY = touchPoint.clientY; + } + func.call(thisObject, e); + // Stop the browser from scrolling/zooming the page. + e.preventDefault(); + }; + for (var i = 0, eventName; + eventName = Blockly.bindEvent_.TOUCH_MAP[name][i]; i++) { + node.addEventListener(eventName, wrapFunc, false); + bindData.push([node, eventName, wrapFunc]); + } + } + return bindData; +}; + +/** + * The TOUCH_MAP lookup dictionary specifies additional touch events to fire, + * in conjunction with mouse events. + * @type {Object} + */ +Blockly.bindEvent_.TOUCH_MAP = {}; +if (goog.events.BrowserFeature.TOUCH_ENABLED) { + Blockly.bindEvent_.TOUCH_MAP = { + 'mousedown': ['touchstart'], + 'mousemove': ['touchmove'], + 'mouseup': ['touchend', 'touchcancel'] + }; +} + +/** + * Unbind one or more events event from a function call. + * @param {!Array.} bindData Opaque data from bindEvent_. This list is + * emptied during the course of calling this function. + * @return {!Function} The function call. + * @private + */ +Blockly.unbindEvent_ = function(bindData) { + while (bindData.length) { + var bindDatum = bindData.pop(); + var node = bindDatum[0]; + var name = bindDatum[1]; + var func = bindDatum[2]; + node.removeEventListener(name, func, false); + } + return func; +}; + +/** + * Don't do anything for this event, just halt propagation. + * @param {!Event} e An event. + */ +Blockly.noEvent = function(e) { + // This event has been handled. No need to bubble up to the document. + e.preventDefault(); + e.stopPropagation(); +}; + +/** + * Is this event targeting a text input widget? + * @param {!Event} e An event. + * @return {boolean} True if text input. + * @private + */ +Blockly.isTargetInput_ = function(e) { + return e.target.type == 'textarea' || e.target.type == 'text' || + e.target.type == 'number' || e.target.type == 'email' || + e.target.type == 'password' || e.target.type == 'search' || + e.target.type == 'tel' || e.target.type == 'url' || + e.target.isContentEditable; +}; + +/** + * Return the coordinates of the top-left corner of this element relative to + * its parent. Only for SVG elements and children (e.g. rect, g, path). + * @param {!Element} element SVG element to find the coordinates of. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + * @private + */ +Blockly.getRelativeXY_ = function(element) { + var xy = new goog.math.Coordinate(0, 0); + // First, check for x and y attributes. + var x = element.getAttribute('x'); + if (x) { + xy.x = parseInt(x, 10); + } + var y = element.getAttribute('y'); + if (y) { + xy.y = parseInt(y, 10); + } + // Second, check for transform="translate(...)" attribute. + var transform = element.getAttribute('transform'); + var r = transform && transform.match(Blockly.getRelativeXY_.XY_REGEXP_); + if (r) { + xy.x += parseFloat(r[1]); + if (r[3]) { + xy.y += parseFloat(r[3]); + } + } + return xy; +}; + +/** + * Static regex to pull the x,y values out of an SVG translate() directive. + * Note that Firefox and IE (9,10) return 'translate(12)' instead of + * 'translate(12, 0)'. + * Note that IE (9,10) returns 'translate(16 8)' instead of 'translate(16, 8)'. + * Note that IE has been reported to return scientific notation (0.123456e-42). + * @type {!RegExp} + * @private + */ +Blockly.getRelativeXY_.XY_REGEXP_ = + /translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*\))?/; + +/** + * Return the absolute coordinates of the top-left corner of this element, + * scales that after canvas SVG element, if it's a descendant. + * The origin (0,0) is the top-left corner of the Blockly SVG. + * @param {!Element} element Element to find the coordinates of. + * @param {!Blockly.Workspace} workspace Element must be in this workspace. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + * @private + */ +Blockly.getSvgXY_ = function(element, workspace) { + var x = 0; + var y = 0; + var scale = 1; + if (goog.dom.contains(workspace.getCanvas(), element) || + goog.dom.contains(workspace.getBubbleCanvas(), element)) { + // Before the SVG canvas, scale the coordinates. + scale = workspace.scale; + } + do { + // Loop through this block and every parent. + var xy = Blockly.getRelativeXY_(element); + if (element == workspace.getCanvas() || + element == workspace.getBubbleCanvas()) { + // After the SVG canvas, don't scale the coordinates. + scale = 1; + } + x += xy.x * scale; + y += xy.y * scale; + element = element.parentNode; + } while (element && element != workspace.getParentSvg()); + return new goog.math.Coordinate(x, y); +}; + +/** + * Helper method for creating SVG elements. + * @param {string} name Element's tag name. + * @param {!Object} attrs Dictionary of attribute names and values. + * @param {Element} parent Optional parent on which to append the element. + * @param {Blockly.Workspace=} opt_workspace Optional workspace for access to + * context (scale...). + * @return {!SVGElement} Newly created SVG element. + */ +Blockly.createSvgElement = function(name, attrs, parent, opt_workspace) { + var e = /** @type {!SVGElement} */ ( + document.createElementNS(Blockly.SVG_NS, name)); + for (var key in attrs) { + e.setAttribute(key, attrs[key]); + } + // IE defines a unique attribute "runtimeStyle", it is NOT applied to + // elements created with createElementNS. However, Closure checks for IE + // and assumes the presence of the attribute and crashes. + if (document.body.runtimeStyle) { // Indicates presence of IE-only attr. + e.runtimeStyle = e.currentStyle = e.style; + } + if (parent) { + parent.appendChild(e); + } + return e; +}; + +/** + * Is this event a right-click? + * @param {!Event} e Mouse event. + * @return {boolean} True if right-click. + */ +Blockly.isRightButton = function(e) { + if (e.ctrlKey && goog.userAgent.MAC) { + // Control-clicking on Mac OS X is treated as a right-click. + // WebKit on Mac OS X fails to change button to 2 (but Gecko does). + return true; + } + return e.button == 2; +}; + +/** + * Return the converted coordinates of the given mouse event. + * The origin (0,0) is the top-left corner of the Blockly svg. + * @param {!Event} e Mouse event. + * @param {!Element} svg SVG element. + * @param {SVGMatrix} matrix Inverted screen CTM to use. + * @return {!Object} Object with .x and .y properties. + */ +Blockly.mouseToSvg = function(e, svg, matrix) { + var svgPoint = svg.createSVGPoint(); + svgPoint.x = e.clientX; + svgPoint.y = e.clientY; + + if (!matrix) { + matrix = svg.getScreenCTM().inverse(); + } + return svgPoint.matrixTransform(matrix); +}; + +/** + * Given an array of strings, return the length of the shortest one. + * @param {!Array.} array Array of strings. + * @return {number} Length of shortest string. + */ +Blockly.shortestStringLength = function(array) { + if (!array.length) { + return 0; + } + var len = array[0].length; + for (var i = 1; i < array.length; i++) { + len = Math.min(len, array[i].length); + } + return len; +}; + +/** + * Given an array of strings, return the length of the common prefix. + * Words may not be split. Any space after a word is included in the length. + * @param {!Array.} array Array of strings. + * @param {number=} opt_shortest Length of shortest string. + * @return {number} Length of common prefix. + */ +Blockly.commonWordPrefix = function(array, opt_shortest) { + if (!array.length) { + return 0; + } else if (array.length == 1) { + return array[0].length; + } + var wordPrefix = 0; + var max = opt_shortest || Blockly.shortestStringLength(array); + for (var len = 0; len < max; len++) { + var letter = array[0][len]; + for (var i = 1; i < array.length; i++) { + if (letter != array[i][len]) { + return wordPrefix; + } + } + if (letter == ' ') { + wordPrefix = len + 1; + } + } + for (var i = 1; i < array.length; i++) { + var letter = array[i][len]; + if (letter && letter != ' ') { + return wordPrefix; + } + } + return max; +}; + +/** + * Given an array of strings, return the length of the common suffix. + * Words may not be split. Any space after a word is included in the length. + * @param {!Array.} array Array of strings. + * @param {number=} opt_shortest Length of shortest string. + * @return {number} Length of common suffix. + */ +Blockly.commonWordSuffix = function(array, opt_shortest) { + if (!array.length) { + return 0; + } else if (array.length == 1) { + return array[0].length; + } + var wordPrefix = 0; + var max = opt_shortest || Blockly.shortestStringLength(array); + for (var len = 0; len < max; len++) { + var letter = array[0].substr(-len - 1, 1); + for (var i = 1; i < array.length; i++) { + if (letter != array[i].substr(-len - 1, 1)) { + return wordPrefix; + } + } + if (letter == ' ') { + wordPrefix = len + 1; + } + } + for (var i = 1; i < array.length; i++) { + var letter = array[i].charAt(array[i].length - len - 1); + if (letter && letter != ' ') { + return wordPrefix; + } + } + return max; +}; + +/** + * Is the given string a number (includes negative and decimals). + * @param {string} str Input string. + * @return {boolean} True if number, false otherwise. + */ +Blockly.isNumber = function(str) { + return !!str.match(/^\s*-?\d+(\.\d+)?\s*$/); +}; + +/** + * Parse a string with any number of interpolation tokens (%1, %2, ...). + * '%' characters may be self-escaped (%%). + * @param {string} message Text containing interpolation tokens. + * @return {!Array.} Array of strings and numbers. + */ +Blockly.utils.tokenizeInterpolation = function(message) { + var tokens = []; + var chars = message.split(''); + chars.push(''); // End marker. + // Parse the message with a finite state machine. + // 0 - Base case. + // 1 - % found. + // 2 - Digit found. + var state = 0; + var buffer = []; + var number = null; + for (var i = 0; i < chars.length; i++) { + var c = chars[i]; + if (state == 0) { + if (c == '%') { + state = 1; // Start escape. + } else { + buffer.push(c); // Regular char. + } + } else if (state == 1) { + if (c == '%') { + buffer.push(c); // Escaped %: %% + state = 0; + } else if ('0' <= c && c <= '9') { + state = 2; + number = c; + var text = buffer.join(''); + if (text) { + tokens.push(text); + } + buffer.length = 0; + } else { + buffer.push('%', c); // Not an escape: %a + state = 0; + } + } else if (state == 2) { + if ('0' <= c && c <= '9') { + number += c; // Multi-digit number. + } else { + tokens.push(parseInt(number, 10)); + i--; // Parse this char again. + state = 0; + } + } + } + var text = buffer.join(''); + if (text) { + tokens.push(text); + } + return tokens; +}; + +/** + * Generate a unique ID. This should be globally unique. + * 87 characters ^ 20 length > 128 bits (better than a UUID). + * @return {string} A globally unique ID string. + */ +Blockly.genUid = function() { + var length = 20; + var soupLength = Blockly.genUid.soup_.length; + var id = []; + for (var i = 0; i < length; i++) { + id[i] = Blockly.genUid.soup_.charAt(Math.random() * soupLength); + } + return id.join(''); +}; + +/** + * Legal characters for the unique ID. + * Should be all on a US keyboard. No XML special characters or control codes. + * Removed $ due to issue 251. + * @private + */ +Blockly.genUid.soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' + + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + +/** + * Wrap text to the specified width. + * @param {string} text Text to wrap. + * @param {number} limit Width to wrap each line. + * @return {string} Wrapped text. + */ +Blockly.utils.wrap = function(text, limit) { + var lines = text.split('\n'); + for (var i = 0; i < lines.length; i++) { + lines[i] = Blockly.utils.wrap_line_(lines[i], limit); + } + return lines.join('\n'); +}; + +/** + * Wrap single line of text to the specified width. + * @param {string} text Text to wrap. + * @param {number} limit Width to wrap each line. + * @return {string} Wrapped text. + * @private + */ +Blockly.utils.wrap_line_ = function(text, limit) { + if (text.length <= limit) { + // Short text, no need to wrap. + return text; + } + // Split the text into words. + var words = text.trim().split(/\s+/); + // Set limit to be the length of the largest word. + for (var i = 0; i < words.length; i++) { + if (words[i].length > limit) { + limit = words[i].length; + } + } + + var lastScore; + var score = -Infinity; + var lastText; + var lineCount = 1; + do { + lastScore = score; + lastText = text; + // Create a list of booleans representing if a space (false) or + // a break (true) appears after each word. + var wordBreaks = []; + // Seed the list with evenly spaced linebreaks. + var steps = words.length / lineCount; + var insertedBreaks = 1; + for (var i = 0; i < words.length - 1; i++) { + if (insertedBreaks < (i + 1.5) / steps) { + insertedBreaks++; + wordBreaks[i] = true; + } else { + wordBreaks[i] = false; + } + } + wordBreaks = Blockly.utils.wrapMutate_(words, wordBreaks, limit); + score = Blockly.utils.wrapScore_(words, wordBreaks, limit); + text = Blockly.utils.wrapToText_(words, wordBreaks); + lineCount++; + } while (score > lastScore); + return lastText; +}; + +/** + * Compute a score for how good the wrapping is. + * @param {!Array.} words Array of each word. + * @param {!Array.} wordBreaks Array of line breaks. + * @param {number} limit Width to wrap each line. + * @return {number} Larger the better. + * @private + */ +Blockly.utils.wrapScore_ = function(words, wordBreaks, limit) { + // If this function becomes a performance liability, add caching. + // Compute the length of each line. + var lineLengths = [0]; + var linePunctuation = []; + for (var i = 0; i < words.length; i++) { + lineLengths[lineLengths.length - 1] += words[i].length; + if (wordBreaks[i] === true) { + lineLengths.push(0); + linePunctuation.push(words[i].charAt(words[i].length - 1)); + } else if (wordBreaks[i] === false) { + lineLengths[lineLengths.length - 1]++; + } + } + var maxLength = Math.max.apply(Math, lineLengths); + + var score = 0; + for (var i = 0; i < lineLengths.length; i++) { + // Optimize for width. + // -2 points per char over limit (scaled to the power of 1.5). + score -= Math.pow(Math.abs(limit - lineLengths[i]), 1.5) * 2; + // Optimize for even lines. + // -1 point per char smaller than max (scaled to the power of 1.5). + score -= Math.pow(maxLength - lineLengths[i], 1.5); + // Optimize for structure. + // Add score to line endings after punctuation. + if ('.?!'.indexOf(linePunctuation[i]) != -1) { + score += limit / 3; + } else if (',;)]}'.indexOf(linePunctuation[i]) != -1) { + score += limit / 4; + } + } + // All else being equal, the last line should not be longer than the + // previous line. For example, this looks wrong: + // aaa bbb + // ccc ddd eee + if (lineLengths.length > 1 && lineLengths[lineLengths.length - 1] <= + lineLengths[lineLengths.length - 2]) { + score += 0.5; + } + return score; +}; + +/** + * Mutate the array of line break locations until an optimal solution is found. + * No line breaks are added or deleted, they are simply moved around. + * @param {!Array.} words Array of each word. + * @param {!Array.} wordBreaks Array of line breaks. + * @param {number} limit Width to wrap each line. + * @return {!Array.} New array of optimal line breaks. + * @private + */ +Blockly.utils.wrapMutate_ = function(words, wordBreaks, limit) { + var bestScore = Blockly.utils.wrapScore_(words, wordBreaks, limit); + var bestBreaks; + // Try shifting every line break forward or backward. + for (var i = 0; i < wordBreaks.length - 1; i++) { + if (wordBreaks[i] == wordBreaks[i + 1]) { + continue; + } + var mutatedWordBreaks = [].concat(wordBreaks); + mutatedWordBreaks[i] = !mutatedWordBreaks[i]; + mutatedWordBreaks[i + 1] = !mutatedWordBreaks[i + 1]; + var mutatedScore = + Blockly.utils.wrapScore_(words, mutatedWordBreaks, limit); + if (mutatedScore > bestScore) { + bestScore = mutatedScore; + bestBreaks = mutatedWordBreaks; + } + } + if (bestBreaks) { + // Found an improvement. See if it may be improved further. + return Blockly.utils.wrapMutate_(words, bestBreaks, limit); + } + // No improvements found. Done. + return wordBreaks; +}; + +/** + * Reassemble the array of words into text, with the specified line breaks. + * @param {!Array.} words Array of each word. + * @param {!Array.} wordBreaks Array of line breaks. + * @return {string} Plain text. + * @private + */ +Blockly.utils.wrapToText_ = function(words, wordBreaks) { + var text = []; + for (var i = 0; i < words.length; i++) { + text.push(words[i]); + if (wordBreaks[i] !== undefined) { + text.push(wordBreaks[i] ? '\n' : ' '); + } + } + return text.join(''); +}; diff --git a/src/blockly/core/variables.js b/src/blockly/core/variables.js new file mode 100644 index 0000000..00c38ad --- /dev/null +++ b/src/blockly/core/variables.js @@ -0,0 +1,273 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Utility functions for handling variables. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Variables'); + +goog.require('Blockly.Blocks'); +goog.require('Blockly.Workspace'); +goog.require('goog.string'); + + +/** + * Category to separate variable names from procedures and generated functions. + */ +Blockly.Variables.NAME_TYPE = 'VARIABLE'; + +/** + * Find all user-created variables that are in use in the workspace. + * For use by generators. + * @param {!Blockly.Block|!Blockly.Workspace} root Root block or workspace. + * @return {!Array.} Array of variable names. + */ +Blockly.Variables.allUsedVariables = function(root) { + var blocks; + if (root instanceof Blockly.Block) { + // Root is Block. + blocks = root.getDescendants(); + } else if (root.getAllBlocks) { + // Root is Workspace. + blocks = root.getAllBlocks(); + } else { + throw 'Not Block or Workspace: ' + root; + } + var variableHash = Object.create(null); + // Iterate through every block and add each variable to the hash. + for (var x = 0; x < blocks.length; x++) { + var blockVariables = blocks[x].getVars(); + if (blockVariables) { + for (var y = 0; y < blockVariables.length; y++) { + var varName = blockVariables[y]; + // Variable name may be null if the block is only half-built. + if (varName) { + variableHash[varName.toLowerCase()] = varName; + } + } + } + } + // Flatten the hash into a list. + var variableList = []; + for (var name in variableHash) { + variableList.push(variableHash[name]); + } + return variableList; +}; + +/** + * Find all variables that the user has created through the workspace or + * toolbox. For use by generators. + * @param {!Blockly.Workspace} root The workspace to inspect. + * @return {!Array.} Array of variable names. + */ +Blockly.Variables.allVariables = function(root) { + if (root instanceof Blockly.Block) { + // Root is Block. + console.warn('Deprecated call to Blockly.Variables.allVariables ' + + 'with a block instead of a workspace. You may want ' + + 'Blockly.Variables.allUsedVariables'); + } + return root.variableList; +}; + +/** + * Construct the blocks required by the flyout for the variable category. + * @param {!Blockly.Workspace} workspace The workspace contianing variables. + * @return {!Array.} Array of XML block elements. + */ +Blockly.Variables.flyoutCategory = function(workspace) { + var variableList = workspace.variableList; + variableList.sort(goog.string.caseInsensitiveCompare); + + var xmlList = []; + var button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_VARIABLE); + xmlList.push(button); + + if (variableList.length > 0) { + if (Blockly.Blocks['variables_set']) { + // + // item + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', 'variables_set'); + if (Blockly.Blocks['math_change']) { + block.setAttribute('gap', 8); + } else { + block.setAttribute('gap', 24); + } + var field = goog.dom.createDom('field', null, variableList[0]); + field.setAttribute('name', 'VAR'); + block.appendChild(field); + xmlList.push(block); + } + if (Blockly.Blocks['math_change']) { + // + // + // + // 1 + // + // + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', 'math_change'); + if (Blockly.Blocks['variables_get']) { + block.setAttribute('gap', 20); + } + var value = goog.dom.createDom('value'); + value.setAttribute('name', 'DELTA'); + block.appendChild(value); + + var field = goog.dom.createDom('field', null, variableList[0]); + field.setAttribute('name', 'VAR'); + block.appendChild(field); + + var shadowBlock = goog.dom.createDom('shadow'); + shadowBlock.setAttribute('type', 'math_number'); + value.appendChild(shadowBlock); + + var numberField = goog.dom.createDom('field', null, '1'); + numberField.setAttribute('name', 'NUM'); + shadowBlock.appendChild(numberField); + + xmlList.push(block); + } + + for (var i = 0; i < variableList.length; i++) { + if (Blockly.Blocks['variables_get']) { + // + // item + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', 'variables_get'); + if (Blockly.Blocks['variables_set']) { + block.setAttribute('gap', 8); + } + var field = goog.dom.createDom('field', null, variableList[i]); + field.setAttribute('name', 'VAR'); + block.appendChild(field); + xmlList.push(block); + } + } + } + return xmlList; +}; + +/** +* Return a new variable name that is not yet being used. This will try to +* generate single letter variable names in the range 'i' to 'z' to start with. +* If no unique name is located it will try 'i' to 'z', 'a' to 'h', +* then 'i2' to 'z2' etc. Skip 'l'. + * @param {!Blockly.Workspace} workspace The workspace to be unique in. +* @return {string} New variable name. +*/ +Blockly.Variables.generateUniqueName = function(workspace) { + var variableList = workspace.variableList; + var newName = ''; + if (variableList.length) { + var nameSuffix = 1; + var letters = 'ijkmnopqrstuvwxyzabcdefgh'; // No 'l'. + var letterIndex = 0; + var potName = letters.charAt(letterIndex); + while (!newName) { + var inUse = false; + for (var i = 0; i < variableList.length; i++) { + if (variableList[i].toLowerCase() == potName) { + // This potential name is already used. + inUse = true; + break; + } + } + if (inUse) { + // Try the next potential name. + letterIndex++; + if (letterIndex == letters.length) { + // Reached the end of the character sequence so back to 'i'. + // a new suffix. + letterIndex = 0; + nameSuffix++; + } + potName = letters.charAt(letterIndex); + if (nameSuffix > 1) { + potName += nameSuffix; + } + } else { + // We can use the current potential name. + newName = potName; + } + } + } else { + newName = 'i'; + } + return newName; +}; + +/** + * Create a new variable on the given workspace. + * @param {!Blockly.Workspace} workspace The workspace on which to create the + * variable. + * @return {null|undefined|string} An acceptable new variable name, or null if + * change is to be aborted (cancel button), or undefined if an existing + * variable was chosen. + */ +Blockly.Variables.createVariable = function(workspace) { + while (true) { + var text = Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, ''); + if (text) { + if (workspace.variableIndexOf(text) != -1) { + window.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1', + text.toLowerCase())); + } else { + workspace.createVariable(text); + break; + } + } else { + text = null; + break; + } + } + return text; +}; + +/** + * Prompt the user for a new variable name. + * @param {string} promptText The string of the prompt. + * @param {string} defaultText The default value to show in the prompt's field. + * @return {?string} The new variable name, or null if the user picked + * something illegal. + */ +Blockly.Variables.promptName = function(promptText, defaultText) { + var newVar = window.prompt(promptText, defaultText); + // Merge runs of whitespace. Strip leading and trailing whitespace. + // Beyond this, all names are legal. + if (newVar) { + newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); + if (newVar == Blockly.Msg.RENAME_VARIABLE || + newVar == Blockly.Msg.NEW_VARIABLE) { + // Ok, not ALL names are legal... + newVar = null; + } + } + return newVar; +}; diff --git a/src/blockly/core/warning.js b/src/blockly/core/warning.js new file mode 100644 index 0000000..bffbf06 --- /dev/null +++ b/src/blockly/core/warning.js @@ -0,0 +1,185 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing a warning. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Warning'); + +goog.require('Blockly.Bubble'); +goog.require('Blockly.Icon'); + + +/** + * Class for a warning. + * @param {!Blockly.Block} block The block associated with this warning. + * @extends {Blockly.Icon} + * @constructor + */ +Blockly.Warning = function(block) { + Blockly.Warning.superClass_.constructor.call(this, block); + this.createIcon(); + // The text_ object can contain multiple warnings. + this.text_ = {}; +}; +goog.inherits(Blockly.Warning, Blockly.Icon); + +/** + * Does this icon get hidden when the block is collapsed. + */ +Blockly.Warning.prototype.collapseHidden = false; + +/** + * Draw the warning icon. + * @param {!Element} group The icon group. + * @private + */ +Blockly.Warning.prototype.drawIcon_ = function(group) { + // Triangle with rounded corners. + Blockly.createSvgElement('path', + {'class': 'blocklyIconShape', + 'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z'}, + group); + // Can't use a real '!' text character since different browsers and operating + // systems render it differently. + // Body of exclamation point. + Blockly.createSvgElement('path', + {'class': 'blocklyIconSymbol', + 'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z'}, + group); + // Dot of exclamation point. + Blockly.createSvgElement('rect', + {'class': 'blocklyIconSymbol', + 'x': '7', 'y': '11', 'height': '2', 'width': '2'}, + group); +}; + +/** + * Create the text for the warning's bubble. + * @param {string} text The text to display. + * @return {!SVGTextElement} The top-level node of the text. + * @private + */ +Blockly.Warning.textToDom_ = function(text) { + var paragraph = /** @type {!SVGTextElement} */ ( + Blockly.createSvgElement('text', + {'class': 'blocklyText blocklyBubbleText', + 'y': Blockly.Bubble.BORDER_WIDTH}, + null)); + var lines = text.split('\n'); + for (var i = 0; i < lines.length; i++) { + var tspanElement = Blockly.createSvgElement('tspan', + {'dy': '1em', 'x': Blockly.Bubble.BORDER_WIDTH}, paragraph); + var textNode = document.createTextNode(lines[i]); + tspanElement.appendChild(textNode); + } + return paragraph; +}; + +/** + * Show or hide the warning bubble. + * @param {boolean} visible True if the bubble should be visible. + */ +Blockly.Warning.prototype.setVisible = function(visible) { + if (visible == this.isVisible()) { + // No change. + return; + } + Blockly.Events.fire( + new Blockly.Events.Ui(this.block_, 'warningOpen', !visible, visible)); + if (visible) { + // Create the bubble to display all warnings. + var paragraph = Blockly.Warning.textToDom_(this.getText()); + this.bubble_ = new Blockly.Bubble( + /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace), + paragraph, this.block_.svgPath_, this.iconXY_, null, null); + if (this.block_.RTL) { + // Right-align the paragraph. + // This cannot be done until the bubble is rendered on screen. + var maxWidth = paragraph.getBBox().width; + for (var i = 0, textElement; textElement = paragraph.childNodes[i]; i++) { + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH); + } + } + this.updateColour(); + // Bump the warning into the right location. + var size = this.bubble_.getBubbleSize(); + this.bubble_.setBubbleSize(size.width, size.height); + } else { + // Dispose of the bubble. + this.bubble_.dispose(); + this.bubble_ = null; + this.body_ = null; + } +}; + +/** + * Bring the warning to the top of the stack when clicked on. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.Warning.prototype.bodyFocus_ = function(e) { + this.bubble_.promote_(); +}; + +/** + * Set this warning's text. + * @param {string} text Warning text (or '' to delete). + * @param {string} id An ID for this text entry to be able to maintain + * multiple warnings. + */ +Blockly.Warning.prototype.setText = function(text, id) { + if (this.text_[id] == text) { + return; + } + if (text) { + this.text_[id] = text; + } else { + delete this.text_[id]; + } + if (this.isVisible()) { + this.setVisible(false); + this.setVisible(true); + } +}; + +/** + * Get this warning's texts. + * @return {string} All texts concatenated into one string. + */ +Blockly.Warning.prototype.getText = function() { + var allWarnings = []; + for (var id in this.text_) { + allWarnings.push(this.text_[id]); + } + return allWarnings.join('\n'); +}; + +/** + * Dispose of this warning. + */ +Blockly.Warning.prototype.dispose = function() { + this.block_.warning = null; + Blockly.Icon.prototype.dispose.call(this); +}; diff --git a/src/blockly/core/widgetdiv.js b/src/blockly/core/widgetdiv.js new file mode 100644 index 0000000..a811339 --- /dev/null +++ b/src/blockly/core/widgetdiv.js @@ -0,0 +1,152 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview A div that floats on top of Blockly. This singleton contains + * temporary HTML UI widgets that the user is currently interacting with. + * E.g. text input areas, colour pickers, context menus. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.WidgetDiv'); + +goog.require('Blockly.Css'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.style'); + + +/** + * The HTML container. Set once by Blockly.WidgetDiv.createDom. + * @type {Element} + */ +Blockly.WidgetDiv.DIV = null; + +/** + * The object currently using this container. + * @type {Object} + * @private + */ +Blockly.WidgetDiv.owner_ = null; + +/** + * Optional cleanup function set by whichever object uses the widget. + * @type {Function} + * @private + */ +Blockly.WidgetDiv.dispose_ = null; + +/** + * Create the widget div and inject it onto the page. + */ +Blockly.WidgetDiv.createDom = function() { + if (Blockly.WidgetDiv.DIV) { + return; // Already created. + } + // Create an HTML container for popup overlays (e.g. editor widgets). + Blockly.WidgetDiv.DIV = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyWidgetDiv'); + document.body.appendChild(Blockly.WidgetDiv.DIV); +}; + +/** + * Initialize and display the widget div. Close the old one if needed. + * @param {!Object} newOwner The object that will be using this container. + * @param {boolean} rtl Right-to-left (true) or left-to-right (false). + * @param {Function} dispose Optional cleanup function to be run when the widget + * is closed. + */ +Blockly.WidgetDiv.show = function(newOwner, rtl, dispose) { + Blockly.WidgetDiv.hide(); + Blockly.WidgetDiv.owner_ = newOwner; + Blockly.WidgetDiv.dispose_ = dispose; + // Temporarily move the widget to the top of the screen so that it does not + // cause a scrollbar jump in Firefox when displayed. + var xy = goog.style.getViewportPageOffset(document); + Blockly.WidgetDiv.DIV.style.top = xy.y + 'px'; + Blockly.WidgetDiv.DIV.style.direction = rtl ? 'rtl' : 'ltr'; + Blockly.WidgetDiv.DIV.style.display = 'block'; +}; + +/** + * Destroy the widget and hide the div. + */ +Blockly.WidgetDiv.hide = function() { + if (Blockly.WidgetDiv.owner_) { + Blockly.WidgetDiv.owner_ = null; + Blockly.WidgetDiv.DIV.style.display = 'none'; + Blockly.WidgetDiv.DIV.style.left = ''; + Blockly.WidgetDiv.DIV.style.top = ''; + Blockly.WidgetDiv.dispose_ && Blockly.WidgetDiv.dispose_(); + Blockly.WidgetDiv.dispose_ = null; + goog.dom.removeChildren(Blockly.WidgetDiv.DIV); + } +}; + +/** + * Is the container visible? + * @return {boolean} True if visible. + */ +Blockly.WidgetDiv.isVisible = function() { + return !!Blockly.WidgetDiv.owner_; +}; + +/** + * Destroy the widget and hide the div if it is being used by the specified + * object. + * @param {!Object} oldOwner The object that was using this container. + */ +Blockly.WidgetDiv.hideIfOwner = function(oldOwner) { + if (Blockly.WidgetDiv.owner_ == oldOwner) { + Blockly.WidgetDiv.hide(); + } +}; + +/** + * Position the widget at a given location. Prevent the widget from going + * offscreen top or left (right in RTL). + * @param {number} anchorX Horizontal location (window coorditates, not body). + * @param {number} anchorY Vertical location (window coorditates, not body). + * @param {!goog.math.Size} windowSize Height/width of window. + * @param {!goog.math.Coordinate} scrollOffset X/y of window scrollbars. + * @param {boolean} rtl True if RTL, false if LTR. + */ +Blockly.WidgetDiv.position = function(anchorX, anchorY, windowSize, + scrollOffset, rtl) { + // Don't let the widget go above the top edge of the window. + if (anchorY < scrollOffset.y) { + anchorY = scrollOffset.y; + } + if (rtl) { + // Don't let the widget go right of the right edge of the window. + if (anchorX > windowSize.width + scrollOffset.x) { + anchorX = windowSize.width + scrollOffset.x; + } + } else { + // Don't let the widget go left of the left edge of the window. + if (anchorX < scrollOffset.x) { + anchorX = scrollOffset.x; + } + } + Blockly.WidgetDiv.DIV.style.left = anchorX + 'px'; + Blockly.WidgetDiv.DIV.style.top = anchorY + 'px'; + Blockly.WidgetDiv.DIV.style.height = windowSize.height + 'px'; +}; diff --git a/src/blockly/core/workspace.js b/src/blockly/core/workspace.js new file mode 100644 index 0000000..a8e97c0 --- /dev/null +++ b/src/blockly/core/workspace.js @@ -0,0 +1,501 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing a workspace. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Workspace'); + +goog.require('goog.math'); + + +/** + * Class for a workspace. This is a data structure that contains blocks. + * There is no UI, and can be created headlessly. + * @param {Blockly.Options} opt_options Dictionary of options. + * @constructor + */ +Blockly.Workspace = function(opt_options) { + /** @type {string} */ + this.id = Blockly.genUid(); + Blockly.Workspace.WorkspaceDB_[this.id] = this; + /** @type {!Blockly.Options} */ + this.options = opt_options || {}; + /** @type {boolean} */ + this.RTL = !!this.options.RTL; + /** @type {boolean} */ + this.horizontalLayout = !!this.options.horizontalLayout; + /** @type {number} */ + this.toolboxPosition = this.options.toolboxPosition; + + /** + * @type {!Array.} + * @private + */ + this.topBlocks_ = []; + /** + * @type {!Array.} + * @private + */ + this.listeners_ = []; + /** + * @type {!Array.} + * @private + */ + this.undoStack_ = []; + /** + * @type {!Array.} + * @private + */ + this.redoStack_ = []; + /** + * @type {!Object} + * @private + */ + this.blockDB_ = Object.create(null); + /* + * @type {!Array.} + * A list of all of the named variables in the workspace, including variables + * that are not currently in use. + */ + this.variableList = []; +}; + +/** + * Workspaces may be headless. + * @type {boolean} True if visible. False if headless. + */ +Blockly.Workspace.prototype.rendered = false; + +/** + * Maximum number of undo events in stack. + * @type {number} 0 to turn off undo, Infinity for unlimited. + */ +Blockly.Workspace.prototype.MAX_UNDO = 1024; + +/** + * Dispose of this workspace. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Workspace.prototype.dispose = function() { + this.listeners_.length = 0; + this.clear(); + // Remove from workspace database. + delete Blockly.Workspace.WorkspaceDB_[this.id]; +}; + +/** + * Angle away from the horizontal to sweep for blocks. Order of execution is + * generally top to bottom, but a small angle changes the scan to give a bit of + * a left to right bias (reversed in RTL). Units are in degrees. + * See: http://tvtropes.org/pmwiki/pmwiki.php/Main/DiagonalBilling. + */ +Blockly.Workspace.SCAN_ANGLE = 3; + +/** + * Add a block to the list of top blocks. + * @param {!Blockly.Block} block Block to remove. + */ +Blockly.Workspace.prototype.addTopBlock = function(block) { + this.topBlocks_.push(block); + if (this.isFlyout) { + // This is for the (unlikely) case where you have a variable in a block in + // an always-open flyout. It needs to be possible to edit the block in the + // flyout, so the contents of the dropdown need to be correct. + var variables = Blockly.Variables.allUsedVariables(block); + for (var i = 0; i < variables.length; i++) { + if (this.variableList.indexOf(variables[i]) == -1) { + this.variableList.push(variables[i]); + } + } + } +}; + +/** + * Remove a block from the list of top blocks. + * @param {!Blockly.Block} block Block to remove. + */ +Blockly.Workspace.prototype.removeTopBlock = function(block) { + var found = false; + for (var child, i = 0; child = this.topBlocks_[i]; i++) { + if (child == block) { + this.topBlocks_.splice(i, 1); + found = true; + break; + } + } + if (!found) { + throw 'Block not present in workspace\'s list of top-most blocks.'; + } +}; + +/** + * Finds the top-level blocks and returns them. Blocks are optionally sorted + * by position; top to bottom (with slight LTR or RTL bias). + * @param {boolean} ordered Sort the list if true. + * @return {!Array.} The top-level block objects. + */ +Blockly.Workspace.prototype.getTopBlocks = function(ordered) { + // Copy the topBlocks_ list. + var blocks = [].concat(this.topBlocks_); + if (ordered && blocks.length > 1) { + var offset = Math.sin(goog.math.toRadians(Blockly.Workspace.SCAN_ANGLE)); + if (this.RTL) { + offset *= -1; + } + blocks.sort(function(a, b) { + var aXY = a.getRelativeToSurfaceXY(); + var bXY = b.getRelativeToSurfaceXY(); + return (aXY.y + offset * aXY.x) - (bXY.y + offset * bXY.x); + }); + } + return blocks; +}; + +/** + * Find all blocks in workspace. No particular order. + * @return {!Array.} Array of blocks. + */ +Blockly.Workspace.prototype.getAllBlocks = function() { + var blocks = this.getTopBlocks(false); + for (var i = 0; i < blocks.length; i++) { + blocks.push.apply(blocks, blocks[i].getChildren()); + } + return blocks; +}; + +/** + * Dispose of all blocks in workspace. + */ +Blockly.Workspace.prototype.clear = function() { + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + while (this.topBlocks_.length) { + this.topBlocks_[0].dispose(); + } + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + + this.variableList.length = 0; +}; + +/** + * Walk the workspace and update the list of variables to only contain ones in + * use on the workspace. Use when loading new workspaces from disk. + * @param {boolean} clearList True if the old variable list should be cleared. + */ +Blockly.Workspace.prototype.updateVariableList = function(clearList) { + // TODO: Sort + if (!this.isFlyout) { + // Update the list in place so that the flyout's references stay correct. + if (clearList) { + this.variableList.length = 0; + } + var allVariables = Blockly.Variables.allUsedVariables(this); + for (var i = 0; i < allVariables.length; i++) { + this.createVariable(allVariables[i]); + } + } +}; + +/** + * Rename a variable by updating its name in the variable list. + * TODO: #468 + * @param {string} oldName Variable to rename. + * @param {string} newName New variable name. + */ +Blockly.Workspace.prototype.renameVariable = function(oldName, newName) { + // Find the old name in the list. + var variableIndex = this.variableIndexOf(oldName); + var newVariableIndex = this.variableIndexOf(newName); + + // We might be renaming to an existing name but with different case. If so, + // we will also update all of the blocks using the new name to have the + // correct case. + if (newVariableIndex != -1 && + this.variableList[newVariableIndex] != newName) { + var oldCase = this.variableList[newVariableIndex]; + } + + Blockly.Events.setGroup(true); + var blocks = this.getAllBlocks(); + // Iterate through every block. + for (var i = 0; i < blocks.length; i++) { + blocks[i].renameVar(oldName, newName); + if (oldCase) { + blocks[i].renameVar(oldCase, newName); + } + } + Blockly.Events.setGroup(false); + + + if (variableIndex == newVariableIndex || + variableIndex != -1 && newVariableIndex == -1) { + // Only changing case, or renaming to a completely novel name. + this.variableList[variableIndex] = newName; + } else if (variableIndex != -1 && newVariableIndex != -1) { + // Renaming one existing variable to another existing variable. + this.variableList.splice(variableIndex, 1); + // The case might have changed. + this.variableList[newVariableIndex] = newName; + } else { + this.variableList.push(newName); + console.log('Tried to rename an non-existent variable.'); + } +}; + +/** + * Create a variable with the given name. + * TODO: #468 + * @param {string} name The new variable's name. + */ +Blockly.Workspace.prototype.createVariable = function(name) { + var index = this.variableIndexOf(name); + if (index == -1) { + this.variableList.push(name); + } +}; + +/** + * Find all the uses of a named variable. + * @param {string} name Name of variable. + * @return {!Array.} Array of block usages. + */ +Blockly.Workspace.prototype.getVariableUses = function(name) { + var uses = []; + var blocks = this.getAllBlocks(); + // Iterate through every block and check the name. + for (var i = 0; i < blocks.length; i++) { + var blockVariables = blocks[i].getVars(); + if (blockVariables) { + for (var j = 0; j < blockVariables.length; j++) { + var varName = blockVariables[j]; + // Variable name may be null if the block is only half-built. + if (varName && Blockly.Names.equals(varName, name)) { + uses.push(blocks[i]); + } + } + } + } + return uses; +}; + +/** + * Delete a variables and all of its uses from this workspace. + * @param {string} name Name of variable to delete. + */ +Blockly.Workspace.prototype.deleteVariable = function(name) { + var variableIndex = this.variableIndexOf(name); + if (variableIndex != -1) { + var uses = this.getVariableUses(name); + if (uses.length > 1) { + for (var i = 0, block; block = uses[i]; i++) { + if (block.type == 'procedures_defnoreturn' || + block.type == 'procedures_defreturn') { + var procedureName = block.getFieldValue('NAME'); + window.alert( + Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.replace('%1', name). + replace('%2', procedureName)); + return; + } + } + var ok = window.confirm( + Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1', uses.length). + replace('%2', name)); + if (!ok) { + return; + } + } + + Blockly.Events.setGroup(true); + for (var i = 0; i < uses.length; i++) { + uses[i].dispose(true, false); + } + Blockly.Events.setGroup(false); + this.variableList.splice(variableIndex, 1); + } +}; + +/** + * Check whether a variable exists with the given name. The check is + * case-insensitive. + * @param {string} name The name to check for. + * @return {number} The index of the name in the variable list, or -1 if it is + * not present. + */ +Blockly.Workspace.prototype.variableIndexOf = function(name) { + for (var i = 0, varname; varname = this.variableList[i]; i++) { + if (Blockly.Names.equals(varname, name)) { + return i; + } + } + return -1; +}; + +/** + * Returns the horizontal offset of the workspace. + * Intended for LTR/RTL compatibility in XML. + * Not relevant for a headless workspace. + * @return {number} Width. + */ +Blockly.Workspace.prototype.getWidth = function() { + return 0; +}; + +/** + * Obtain a newly created block. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise + * create a new id. + * @return {!Blockly.Block} The created block. + */ +Blockly.Workspace.prototype.newBlock = function(prototypeName, opt_id) { + return new Blockly.Block(this, prototypeName, opt_id); +}; + +/** + * The number of blocks that may be added to the workspace before reaching + * the maxBlocks. + * @return {number} Number of blocks left. + */ +Blockly.Workspace.prototype.remainingCapacity = function() { + if (isNaN(this.options.maxBlocks)) { + return Infinity; + } + return this.options.maxBlocks - this.getAllBlocks().length; +}; + +/** + * Undo or redo the previous action. + * @param {boolean} redo False if undo, true if redo. + */ +Blockly.Workspace.prototype.undo = function(redo) { + var inputStack = redo ? this.redoStack_ : this.undoStack_; + var outputStack = redo ? this.undoStack_ : this.redoStack_; + var inputEvent = inputStack.pop(); + if (!inputEvent) { + return; + } + var events = [inputEvent]; + // Do another undo/redo if the next one is of the same group. + while (inputStack.length && inputEvent.group && + inputEvent.group == inputStack[inputStack.length - 1].group) { + events.push(inputStack.pop()); + } + // Push these popped events on the opposite stack. + for (var i = 0, event; event = events[i]; i++) { + outputStack.push(event); + } + events = Blockly.Events.filter(events, redo); + Blockly.Events.recordUndo = false; + for (var i = 0, event; event = events[i]; i++) { + event.run(redo); + } + Blockly.Events.recordUndo = true; +}; + +/** + * Clear the undo/redo stacks. + */ +Blockly.Workspace.prototype.clearUndo = function() { + this.undoStack_.length = 0; + this.redoStack_.length = 0; + // Stop any events already in the firing queue from being undoable. + Blockly.Events.clearPendingUndo(); +}; + +/** + * When something in this workspace changes, call a function. + * @param {!Function} func Function to call. + * @return {!Function} Function that can be passed to + * removeChangeListener. + */ +Blockly.Workspace.prototype.addChangeListener = function(func) { + this.listeners_.push(func); + return func; +}; + +/** + * Stop listening for this workspace's changes. + * @param {Function} func Function to stop calling. + */ +Blockly.Workspace.prototype.removeChangeListener = function(func) { + var i = this.listeners_.indexOf(func); + if (i != -1) { + this.listeners_.splice(i, 1); + } +}; + +/** + * Fire a change event. + * @param {!Blockly.Events.Abstract} event Event to fire. + */ +Blockly.Workspace.prototype.fireChangeListener = function(event) { + if (event.recordUndo) { + this.undoStack_.push(event); + this.redoStack_.length = 0; + if (this.undoStack_.length > this.MAX_UNDO) { + this.undoStack_.unshift(); + } + } + for (var i = 0, func; func = this.listeners_[i]; i++) { + func(event); + } +}; + +/** + * Find the block on this workspace with the specified ID. + * @param {string} id ID of block to find. + * @return {Blockly.Block} The sought after block or null if not found. + */ +Blockly.Workspace.prototype.getBlockById = function(id) { + return this.blockDB_[id] || null; +}; + +/** + * Database of all workspaces. + * @private + */ +Blockly.Workspace.WorkspaceDB_ = Object.create(null); + +/** + * Find the workspace with the specified ID. + * @param {string} id ID of workspace to find. + * @return {Blockly.Workspace} The sought after workspace or null if not found. + */ +Blockly.Workspace.getById = function(id) { + return Blockly.Workspace.WorkspaceDB_[id] || null; +}; + +// Export symbols that would otherwise be renamed by Closure compiler. +Blockly.Workspace.prototype['clear'] = Blockly.Workspace.prototype.clear; +Blockly.Workspace.prototype['clearUndo'] = + Blockly.Workspace.prototype.clearUndo; +Blockly.Workspace.prototype['addChangeListener'] = + Blockly.Workspace.prototype.addChangeListener; +Blockly.Workspace.prototype['removeChangeListener'] = + Blockly.Workspace.prototype.removeChangeListener; diff --git a/src/blockly/core/workspace_svg.js b/src/blockly/core/workspace_svg.js new file mode 100644 index 0000000..a3c0b53 --- /dev/null +++ b/src/blockly/core/workspace_svg.js @@ -0,0 +1,1401 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2014 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing a workspace rendered as SVG. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.WorkspaceSvg'); + +// TODO(scr): Fix circular dependencies +//goog.require('Blockly.BlockSvg'); +goog.require('Blockly.ConnectionDB'); +goog.require('Blockly.constants'); +goog.require('Blockly.Options'); +goog.require('Blockly.ScrollbarPair'); +goog.require('Blockly.Trashcan'); +goog.require('Blockly.Workspace'); +goog.require('Blockly.Xml'); +goog.require('Blockly.ZoomControls'); + +goog.require('goog.dom'); +goog.require('goog.math.Coordinate'); +goog.require('goog.userAgent'); + + +/** + * Class for a workspace. This is an onscreen area with optional trashcan, + * scrollbars, bubbles, and dragging. + * @param {!Blockly.Options} options Dictionary of options. + * @extends {Blockly.Workspace} + * @constructor + */ +Blockly.WorkspaceSvg = function(options) { + Blockly.WorkspaceSvg.superClass_.constructor.call(this, options); + this.getMetrics = + options.getMetrics || Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_; + this.setMetrics = + options.setMetrics || Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_; + + Blockly.ConnectionDB.init(this); + + /** + * Database of pre-loaded sounds. + * @private + * @const + */ + this.SOUNDS_ = Object.create(null); +}; +goog.inherits(Blockly.WorkspaceSvg, Blockly.Workspace); + +/** + * Wrapper function called when a resize event occurs. + * @type {Array.} Data that can be passed to unbindEvent_ + */ +Blockly.WorkspaceSvg.prototype.resizeHandlerWrapper_ = null; + +/** + * Svg workspaces are user-visible (as opposed to a headless workspace). + * @type {boolean} True if visible. False if headless. + */ +Blockly.WorkspaceSvg.prototype.rendered = true; + +/** + * Is this workspace the surface for a flyout? + * @type {boolean} + */ +Blockly.WorkspaceSvg.prototype.isFlyout = false; + +/** + * Is this workspace the surface for a mutator? + * @type {boolean} + * @package + */ +Blockly.WorkspaceSvg.prototype.isMutator = false; + +/** + * Is this workspace currently being dragged around? + * DRAG_NONE - No drag operation. + * DRAG_BEGIN - Still inside the initial DRAG_RADIUS. + * DRAG_FREE - Workspace has been dragged further than DRAG_RADIUS. + * @private + */ +Blockly.WorkspaceSvg.prototype.dragMode_ = Blockly.DRAG_NONE; + +/** + * Current horizontal scrolling offset. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.scrollX = 0; + +/** + * Current vertical scrolling offset. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.scrollY = 0; + +/** + * Horizontal scroll value when scrolling started. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.startScrollX = 0; + +/** + * Vertical scroll value when scrolling started. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.startScrollY = 0; + +/** + * Distance from mouse to object being dragged. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.WorkspaceSvg.prototype.dragDeltaXY_ = null; + +/** + * Current scale. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.scale = 1; + +/** + * The workspace's trashcan (if any). + * @type {Blockly.Trashcan} + */ +Blockly.WorkspaceSvg.prototype.trashcan = null; + +/** + * This workspace's scrollbars, if they exist. + * @type {Blockly.ScrollbarPair} + */ +Blockly.WorkspaceSvg.prototype.scrollbar = null; + +/** + * Time that the last sound was played. + * @type {Date} + * @private + */ +Blockly.WorkspaceSvg.prototype.lastSound_ = null; + +/** + * Last known position of the page scroll. + * This is used to determine whether we have recalculated screen coordinate + * stuff since the page scrolled. + * @type {!goog.math.Coordinate} + * @private + */ +Blockly.WorkspaceSvg.prototype.lastRecordedPageScroll_ = null; + +/** + * Inverted screen CTM, for use in mouseToSvg. + * @type {SVGMatrix} + * @private + */ +Blockly.WorkspaceSvg.prototype.inverseScreenCTM_ = null; + +/** + * Getter for the inverted screen CTM. + * @return {SVGMatrix} The matrix to use in mouseToSvg + */ +Blockly.WorkspaceSvg.prototype.getInverseScreenCTM = function() { + return this.inverseScreenCTM_; +}; + +/** + * Update the inverted screen CTM. + */ +Blockly.WorkspaceSvg.prototype.updateInverseScreenCTM = function() { + this.inverseScreenCTM_ = this.getParentSvg().getScreenCTM().inverse(); +}; + +/** + * Save resize handler data so we can delete it later in dispose. + * @param {!Array.} handler Data that can be passed to unbindEvent_. + */ +Blockly.WorkspaceSvg.prototype.setResizeHandlerWrapper = function(handler) { + this.resizeHandlerWrapper_ = handler; +}; + +/** + * Create the workspace DOM elements. + * @param {string=} opt_backgroundClass Either 'blocklyMainBackground' or + * 'blocklyMutatorBackground'. + * @return {!Element} The workspace's SVG group. + */ +Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { + /** + * + * + * [Trashcan and/or flyout may go here] + * + * + * [Scrollbars may go here] + * + * @type {SVGElement} + */ + this.svgGroup_ = Blockly.createSvgElement('g', + {'class': 'blocklyWorkspace'}, null); + if (opt_backgroundClass) { + /** @type {SVGElement} */ + this.svgBackground_ = Blockly.createSvgElement('rect', + {'height': '100%', 'width': '100%', 'class': opt_backgroundClass}, + this.svgGroup_); + if (opt_backgroundClass == 'blocklyMainBackground') { + this.svgBackground_.style.fill = + 'url(#' + this.options.gridPattern.id + ')'; + } + } + /** @type {SVGElement} */ + this.svgBlockCanvas_ = Blockly.createSvgElement('g', + {'class': 'blocklyBlockCanvas'}, this.svgGroup_, this); + /** @type {SVGElement} */ + this.svgBubbleCanvas_ = Blockly.createSvgElement('g', + {'class': 'blocklyBubbleCanvas'}, this.svgGroup_, this); + var bottom = Blockly.Scrollbar.scrollbarThickness; + if (this.options.hasTrashcan) { + bottom = this.addTrashcan_(bottom); + } + if (this.options.zoomOptions && this.options.zoomOptions.controls) { + bottom = this.addZoomControls_(bottom); + } + + if (!this.isFlyout) { + Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_); + var thisWorkspace = this; + Blockly.bindEvent_(this.svgGroup_, 'touchstart', null, + function(e) {Blockly.longStart_(e, thisWorkspace);}); + if (this.options.zoomOptions && this.options.zoomOptions.wheel) { + // Mouse-wheel. + Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.onMouseWheel_); + } + } + + // Determine if there needs to be a category tree, or a simple list of + // blocks. This cannot be changed later, since the UI is very different. + if (this.options.hasCategories) { + this.toolbox_ = new Blockly.Toolbox(this); + } else if (this.options.languageTree) { + this.addFlyout_(); + } + this.updateGridPattern_(); + this.recordDeleteAreas(); + return this.svgGroup_; +}; + +/** + * Dispose of this workspace. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.WorkspaceSvg.prototype.dispose = function() { + // Stop rerendering. + this.rendered = false; + Blockly.WorkspaceSvg.superClass_.dispose.call(this); + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.svgBlockCanvas_ = null; + this.svgBubbleCanvas_ = null; + if (this.toolbox_) { + this.toolbox_.dispose(); + this.toolbox_ = null; + } + if (this.flyout_) { + this.flyout_.dispose(); + this.flyout_ = null; + } + if (this.trashcan) { + this.trashcan.dispose(); + this.trashcan = null; + } + if (this.scrollbar) { + this.scrollbar.dispose(); + this.scrollbar = null; + } + if (this.zoomControls_) { + this.zoomControls_.dispose(); + this.zoomControls_ = null; + } + if (!this.options.parentWorkspace) { + // Top-most workspace. Dispose of the div that the + // svg is injected into (i.e. injectionDiv). + goog.dom.removeNode(this.getParentSvg().parentNode); + } + if (this.resizeHandlerWrapper_) { + Blockly.unbindEvent_(this.resizeHandlerWrapper_); + this.resizeHandlerWrapper_ = null; + } +}; + +/** + * Obtain a newly created block. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise + * create a new id. + * @return {!Blockly.BlockSvg} The created block. + */ +Blockly.WorkspaceSvg.prototype.newBlock = function(prototypeName, opt_id) { + return new Blockly.BlockSvg(this, prototypeName, opt_id); +}; + +/** + * Add a trashcan. + * @param {number} bottom Distance from workspace bottom to bottom of trashcan. + * @return {number} Distance from workspace bottom to the top of trashcan. + * @private + */ +Blockly.WorkspaceSvg.prototype.addTrashcan_ = function(bottom) { + /** @type {Blockly.Trashcan} */ + this.trashcan = new Blockly.Trashcan(this); + var svgTrashcan = this.trashcan.createDom(); + this.svgGroup_.insertBefore(svgTrashcan, this.svgBlockCanvas_); + return this.trashcan.init(bottom); +}; + +/** + * Add zoom controls. + * @param {number} bottom Distance from workspace bottom to bottom of controls. + * @return {number} Distance from workspace bottom to the top of controls. + * @private + */ +Blockly.WorkspaceSvg.prototype.addZoomControls_ = function(bottom) { + /** @type {Blockly.ZoomControls} */ + this.zoomControls_ = new Blockly.ZoomControls(this); + var svgZoomControls = this.zoomControls_.createDom(); + this.svgGroup_.appendChild(svgZoomControls); + return this.zoomControls_.init(bottom); +}; + +/** + * Add a flyout. + * @private + */ +Blockly.WorkspaceSvg.prototype.addFlyout_ = function() { + var workspaceOptions = { + disabledPatternId: this.options.disabledPatternId, + parentWorkspace: this, + RTL: this.RTL, + horizontalLayout: this.horizontalLayout, + toolboxPosition: this.options.toolboxPosition + }; + /** @type {Blockly.Flyout} */ + this.flyout_ = new Blockly.Flyout(workspaceOptions); + this.flyout_.autoClose = false; + var svgFlyout = this.flyout_.createDom(); + this.svgGroup_.insertBefore(svgFlyout, this.svgBlockCanvas_); +}; + +/** + * Update items that use screen coordinate calculations + * because something has changed (e.g. scroll position, window size). + * @private + */ +Blockly.WorkspaceSvg.prototype.updateScreenCalculations_ = function() { + this.updateInverseScreenCTM(); + this.recordDeleteAreas(); +}; + +/** + * Resize the parts of the workspace that change when the workspace + * contents (e.g. block positions) change. This will also scroll the + * workspace contents if needed. + * @package + */ +Blockly.WorkspaceSvg.prototype.resizeContents = function() { + if (this.scrollbar) { + // TODO(picklesrus): Once rachel-fenichel's scrollbar refactoring + // is complete, call the method that only resizes scrollbar + // based on contents. + this.scrollbar.resize(); + } + this.updateInverseScreenCTM(); +}; + +/** + * Resize and reposition all of the workspace chrome (toolbox, + * trash, scrollbars etc.) + * This should be called when something changes that + * requires recalculating dimensions and positions of the + * trash, zoom, toolbox, etc. (e.g. window resize). + */ +Blockly.WorkspaceSvg.prototype.resize = function() { + if (this.toolbox_) { + this.toolbox_.position(); + } + if (this.flyout_) { + this.flyout_.position(); + } + if (this.trashcan) { + this.trashcan.position(); + } + if (this.zoomControls_) { + this.zoomControls_.position(); + } + if (this.scrollbar) { + this.scrollbar.resize(); + } + this.updateScreenCalculations_(); +}; + +/** + * Resizes and repositions workspace chrome if the page has a new + * scroll position. + * @package + */ +Blockly.WorkspaceSvg.prototype.updateScreenCalculationsIfScrolled + = function() { + /* eslint-disable indent */ + var currScroll = goog.dom.getDocumentScroll(); + if (!goog.math.Coordinate.equals(this.lastRecordedPageScroll_, + currScroll)) { + this.lastRecordedPageScroll_ = currScroll; + this.updateScreenCalculations_(); + } +}; /* eslint-enable indent */ + +/** + * Get the SVG element that forms the drawing surface. + * @return {!Element} SVG element. + */ +Blockly.WorkspaceSvg.prototype.getCanvas = function() { + return this.svgBlockCanvas_; +}; + +/** + * Get the SVG element that forms the bubble surface. + * @return {!SVGGElement} SVG element. + */ +Blockly.WorkspaceSvg.prototype.getBubbleCanvas = function() { + return this.svgBubbleCanvas_; +}; + +/** + * Get the SVG element that contains this workspace. + * @return {!Element} SVG element. + */ +Blockly.WorkspaceSvg.prototype.getParentSvg = function() { + if (this.cachedParentSvg_) { + return this.cachedParentSvg_; + } + var element = this.svgGroup_; + while (element) { + if (element.tagName == 'svg') { + this.cachedParentSvg_ = element; + return element; + } + element = element.parentNode; + } + return null; +}; + +/** + * Translate this workspace to new coordinates. + * @param {number} x Horizontal translation. + * @param {number} y Vertical translation. + */ +Blockly.WorkspaceSvg.prototype.translate = function(x, y) { + var translation = 'translate(' + x + ',' + y + ') ' + + 'scale(' + this.scale + ')'; + this.svgBlockCanvas_.setAttribute('transform', translation); + this.svgBubbleCanvas_.setAttribute('transform', translation); +}; + +/** + * Returns the horizontal offset of the workspace. + * Intended for LTR/RTL compatibility in XML. + * @return {number} Width. + */ +Blockly.WorkspaceSvg.prototype.getWidth = function() { + var metrics = this.getMetrics(); + return metrics ? metrics.viewWidth / this.scale : 0; +}; + +/** + * Toggles the visibility of the workspace. + * Currently only intended for main workspace. + * @param {boolean} isVisible True if workspace should be visible. + */ +Blockly.WorkspaceSvg.prototype.setVisible = function(isVisible) { + this.getParentSvg().style.display = isVisible ? 'block' : 'none'; + if (this.toolbox_) { + // Currently does not support toolboxes in mutators. + this.toolbox_.HtmlDiv.style.display = isVisible ? 'block' : 'none'; + } + if (isVisible) { + this.render(); + if (this.toolbox_) { + this.toolbox_.position(); + } + } else { + Blockly.hideChaff(true); + } +}; + +/** + * Render all blocks in workspace. + */ +Blockly.WorkspaceSvg.prototype.render = function() { + // Generate list of all blocks. + var blocks = this.getAllBlocks(); + // Render each block. + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].render(false); + } +}; + +/** + * Turn the visual trace functionality on or off. + * @param {boolean} armed True if the trace should be on. + */ +Blockly.WorkspaceSvg.prototype.traceOn = function(armed) { + this.traceOn_ = armed; + if (this.traceWrapper_) { + Blockly.unbindEvent_(this.traceWrapper_); + this.traceWrapper_ = null; + } + if (armed) { + this.traceWrapper_ = Blockly.bindEvent_(this.svgBlockCanvas_, + 'blocklySelectChange', this, function() {this.traceOn_ = false;}); + } +}; + +/** + * Highlight a block in the workspace. + * @param {?string} id ID of block to find. + */ +Blockly.WorkspaceSvg.prototype.highlightBlock = function(id) { + if (this.traceOn_ && Blockly.dragMode_ != Blockly.DRAG_NONE) { + // The blocklySelectChange event normally prevents this, but sometimes + // there is a race condition on fast-executing apps. + this.traceOn(false); + } + if (!this.traceOn_) { + return; + } + var block = null; + if (id) { + block = this.getBlockById(id); + if (!block) { + return; + } + } + // Temporary turn off the listener for selection changes, so that we don't + // trip the monitor for detecting user activity. + this.traceOn(false); + // Select the current block. + if (block) { + block.select(); + } else if (Blockly.selected) { + Blockly.selected.unselect(); + } + // Restore the monitor for user activity after the selection event has fired. + var thisWorkspace = this; + setTimeout(function() {thisWorkspace.traceOn(true);}, 1); +}; + +/** + * Paste the provided block onto the workspace. + * @param {!Element} xmlBlock XML block element. + */ +Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) { + if (!this.rendered || xmlBlock.getElementsByTagName('block').length >= + this.remainingCapacity()) { + return; + } + Blockly.terminateDrag_(); // Dragging while pasting? No. + Blockly.Events.disable(); + try { + var block = Blockly.Xml.domToBlock(xmlBlock, this); + // Move the duplicate to original position. + var blockX = parseInt(xmlBlock.getAttribute('x'), 10); + var blockY = parseInt(xmlBlock.getAttribute('y'), 10); + if (!isNaN(blockX) && !isNaN(blockY)) { + if (this.RTL) { + blockX = -blockX; + } + // Offset block until not clobbering another block and not in connection + // distance with neighbouring blocks. + do { + var collide = false; + var allBlocks = this.getAllBlocks(); + for (var i = 0, otherBlock; otherBlock = allBlocks[i]; i++) { + var otherXY = otherBlock.getRelativeToSurfaceXY(); + if (Math.abs(blockX - otherXY.x) <= 1 && + Math.abs(blockY - otherXY.y) <= 1) { + collide = true; + break; + } + } + if (!collide) { + // Check for blocks in snap range to any of its connections. + var connections = block.getConnections_(false); + for (var i = 0, connection; connection = connections[i]; i++) { + var neighbour = connection.closest(Blockly.SNAP_RADIUS, + new goog.math.Coordinate(blockX, blockY)); + if (neighbour.connection) { + collide = true; + break; + } + } + } + if (collide) { + if (this.RTL) { + blockX -= Blockly.SNAP_RADIUS; + } else { + blockX += Blockly.SNAP_RADIUS; + } + blockY += Blockly.SNAP_RADIUS * 2; + } + } while (collide); + block.moveBy(blockX, blockY); + } + } finally { + Blockly.Events.enable(); + } + if (Blockly.Events.isEnabled() && !block.isShadow()) { + Blockly.Events.fire(new Blockly.Events.Create(block)); + } + block.select(); +}; + +/** + * Create a new variable with the given name. Update the flyout to show the new + * variable immediately. + * TODO: #468 + * @param {string} name The new variable's name. + */ +Blockly.WorkspaceSvg.prototype.createVariable = function(name) { + Blockly.WorkspaceSvg.superClass_.createVariable.call(this, name); + if (this.toolbox_ && this.toolbox_.flyout_) { + this.toolbox_.refreshSelection(); + } +}; + +/** + * Make a list of all the delete areas for this workspace. + */ +Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() { + if (this.trashcan) { + this.deleteAreaTrash_ = this.trashcan.getClientRect(); + } else { + this.deleteAreaTrash_ = null; + } + if (this.flyout_) { + this.deleteAreaToolbox_ = this.flyout_.getClientRect(); + } else if (this.toolbox_) { + this.deleteAreaToolbox_ = this.toolbox_.getClientRect(); + } else { + this.deleteAreaToolbox_ = null; + } +}; + +/** + * Is the mouse event over a delete area (toolbox or non-closing flyout)? + * Opens or closes the trashcan and sets the cursor as a side effect. + * @param {!Event} e Mouse move event. + * @return {boolean} True if event is in a delete area. + */ +Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) { + var xy = new goog.math.Coordinate(e.clientX, e.clientY); + if (this.deleteAreaTrash_) { + if (this.deleteAreaTrash_.contains(xy)) { + this.trashcan.setOpen_(true); + Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE); + return true; + } + this.trashcan.setOpen_(false); + } + if (this.deleteAreaToolbox_) { + if (this.deleteAreaToolbox_.contains(xy)) { + Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE); + return true; + } + } + Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); + return false; +}; + +/** + * Handle a mouse-down on SVG drawing surface. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) { + this.markFocused(); + if (Blockly.isTargetInput_(e)) { + return; + } + Blockly.terminateDrag_(); // In case mouse-up event was lost. + Blockly.hideChaff(); + var isTargetWorkspace = e.target && e.target.nodeName && + (e.target.nodeName.toLowerCase() == 'svg' || + e.target == this.svgBackground_); + if (isTargetWorkspace && Blockly.selected && !this.options.readOnly) { + // Clicking on the document clears the selection. + Blockly.selected.unselect(); + } + if (Blockly.isRightButton(e)) { + // Right-click. + this.showContextMenu_(e); + } else if (this.scrollbar) { + this.dragMode_ = Blockly.DRAG_BEGIN; + // Record the current mouse position. + this.startDragMouseX = e.clientX; + this.startDragMouseY = e.clientY; + this.startDragMetrics = this.getMetrics(); + this.startScrollX = this.scrollX; + this.startScrollY = this.scrollY; + + // If this is a touch event then bind to the mouseup so workspace drag mode + // is turned off and double move events are not performed on a block. + // See comment in inject.js Blockly.init_ as to why mouseup events are + // bound to the document instead of the SVG's surface. + if ('mouseup' in Blockly.bindEvent_.TOUCH_MAP) { + Blockly.onTouchUpWrapper_ = Blockly.onTouchUpWrapper_ || []; + Blockly.onTouchUpWrapper_ = Blockly.onTouchUpWrapper_.concat( + Blockly.bindEvent_(document, 'mouseup', null, Blockly.onMouseUp_)); + } + Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_ || []; + Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_.concat( + Blockly.bindEvent_(document, 'mousemove', null, Blockly.onMouseMove_)); + } + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); + e.preventDefault(); +}; + +/** + * Start tracking a drag of an object on this workspace. + * @param {!Event} e Mouse down event. + * @param {!goog.math.Coordinate} xy Starting location of object. + */ +Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) { + // Record the starting offset between the bubble's location and the mouse. + var point = Blockly.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); + // Fix scale of mouse event. + point.x /= this.scale; + point.y /= this.scale; + this.dragDeltaXY_ = goog.math.Coordinate.difference(xy, point); +}; + +/** + * Track a drag of an object on this workspace. + * @param {!Event} e Mouse move event. + * @return {!goog.math.Coordinate} New location of object. + */ +Blockly.WorkspaceSvg.prototype.moveDrag = function(e) { + var point = Blockly.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); + // Fix scale of mouse event. + point.x /= this.scale; + point.y /= this.scale; + return goog.math.Coordinate.sum(this.dragDeltaXY_, point); +}; + +/** + * Is the user currently dragging a block or scrolling the flyout/workspace? + * @return {boolean} True if currently dragging or scrolling. + */ +Blockly.WorkspaceSvg.prototype.isDragging = function() { + return Blockly.dragMode_ == Blockly.DRAG_FREE || + (Blockly.Flyout.startFlyout_ && + Blockly.Flyout.startFlyout_.dragMode_ == Blockly.DRAG_FREE) || + this.dragMode_ == Blockly.DRAG_FREE; +}; + +/** + * Handle a mouse-wheel on SVG drawing surface. + * @param {!Event} e Mouse wheel event. + * @private + */ +Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) { + // TODO: Remove terminateDrag and compensate for coordinate skew during zoom. + Blockly.terminateDrag_(); + var delta = e.deltaY > 0 ? -1 : 1; + var position = Blockly.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); + this.zoom(position.x, position.y, delta); + e.preventDefault(); +}; + +/** + * Calculate the bounding box for the blocks on the workspace. + * + * @return {Object} Contains the position and size of the bounding box + * containing the blocks on the workspace. + */ +Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox = function() { + var topBlocks = this.getTopBlocks(false); + // There are no blocks, return empty rectangle. + if (!topBlocks.length) { + return {x: 0, y: 0, width: 0, height: 0}; + } + + // Initialize boundary using the first block. + var boundary = topBlocks[0].getBoundingRectangle(); + + // Start at 1 since the 0th block was used for initialization + for (var i = 1; i < topBlocks.length; i++) { + var blockBoundary = topBlocks[i].getBoundingRectangle(); + if (blockBoundary.topLeft.x < boundary.topLeft.x) { + boundary.topLeft.x = blockBoundary.topLeft.x; + } + if (blockBoundary.bottomRight.x > boundary.bottomRight.x) { + boundary.bottomRight.x = blockBoundary.bottomRight.x; + } + if (blockBoundary.topLeft.y < boundary.topLeft.y) { + boundary.topLeft.y = blockBoundary.topLeft.y; + } + if (blockBoundary.bottomRight.y > boundary.bottomRight.y) { + boundary.bottomRight.y = blockBoundary.bottomRight.y; + } + } + return { + x: boundary.topLeft.x, + y: boundary.topLeft.y, + width: boundary.bottomRight.x - boundary.topLeft.x, + height: boundary.bottomRight.y - boundary.topLeft.y + }; +}; + +/** + * Clean up the workspace by ordering all the blocks in a column. + */ +Blockly.WorkspaceSvg.prototype.cleanUp = function() { + Blockly.Events.setGroup(true); + var topBlocks = this.getTopBlocks(true); + var cursorY = 0; + for (var i = 0, block; block = topBlocks[i]; i++) { + var xy = block.getRelativeToSurfaceXY(); + block.moveBy(-xy.x, cursorY - xy.y); + block.snapToGrid(); + cursorY = block.getRelativeToSurfaceXY().y + + block.getHeightWidth().height + Blockly.BlockSvg.MIN_BLOCK_Y; + } + Blockly.Events.setGroup(false); + // Fire an event to allow scrollbars to resize. + this.resizeContents(); +}; + +/** + * Show the context menu for the workspace. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { + if (this.options.readOnly || this.isFlyout) { + return; + } + var menuOptions = []; + var topBlocks = this.getTopBlocks(true); + var eventGroup = Blockly.genUid(); + + // Options to undo/redo previous action. + var undoOption = {}; + undoOption.text = Blockly.Msg.UNDO; + undoOption.enabled = this.undoStack_.length > 0; + undoOption.callback = this.undo.bind(this, false); + menuOptions.push(undoOption); + var redoOption = {}; + redoOption.text = Blockly.Msg.REDO; + redoOption.enabled = this.redoStack_.length > 0; + redoOption.callback = this.undo.bind(this, true); + menuOptions.push(redoOption); + + // Option to clean up blocks. + if (this.scrollbar) { + var cleanOption = {}; + cleanOption.text = Blockly.Msg.CLEAN_UP; + cleanOption.enabled = topBlocks.length > 1; + cleanOption.callback = this.cleanUp.bind(this); + menuOptions.push(cleanOption); + } + + // Add a little animation to collapsing and expanding. + var DELAY = 10; + if (this.options.collapse) { + var hasCollapsedBlocks = false; + var hasExpandedBlocks = false; + for (var i = 0; i < topBlocks.length; i++) { + var block = topBlocks[i]; + while (block) { + if (block.isCollapsed()) { + hasCollapsedBlocks = true; + } else { + hasExpandedBlocks = true; + } + block = block.getNextBlock(); + } + } + + /** + * Option to collapse or expand top blocks. + * @param {boolean} shouldCollapse Whether a block should collapse. + * @private + */ + var toggleOption = function(shouldCollapse) { + var ms = 0; + for (var i = 0; i < topBlocks.length; i++) { + var block = topBlocks[i]; + while (block) { + setTimeout(block.setCollapsed.bind(block, shouldCollapse), ms); + block = block.getNextBlock(); + ms += DELAY; + } + } + }; + + // Option to collapse top blocks. + var collapseOption = {enabled: hasExpandedBlocks}; + collapseOption.text = Blockly.Msg.COLLAPSE_ALL; + collapseOption.callback = function() { + toggleOption(true); + }; + menuOptions.push(collapseOption); + + // Option to expand top blocks. + var expandOption = {enabled: hasCollapsedBlocks}; + expandOption.text = Blockly.Msg.EXPAND_ALL; + expandOption.callback = function() { + toggleOption(false); + }; + menuOptions.push(expandOption); + } + + // Option to delete all blocks. + // Count the number of blocks that are deletable. + var deleteList = []; + function addDeletableBlocks(block) { + if (block.isDeletable()) { + deleteList = deleteList.concat(block.getDescendants()); + } else { + var children = block.getChildren(); + for (var i = 0; i < children.length; i++) { + addDeletableBlocks(children[i]); + } + } + } + for (var i = 0; i < topBlocks.length; i++) { + addDeletableBlocks(topBlocks[i]); + } + + function deleteNext() { + Blockly.Events.setGroup(eventGroup); + var block = deleteList.shift(); + if (block) { + if (block.workspace) { + block.dispose(false, true); + setTimeout(deleteNext, DELAY); + } else { + deleteNext(); + } + } + Blockly.Events.setGroup(false); + } + + var deleteOption = { + text: deleteList.length == 1 ? Blockly.Msg.DELETE_BLOCK : + Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteList.length)), + enabled: deleteList.length > 0, + callback: function() { + if (deleteList.length < 2 || + window.confirm(Blockly.Msg.DELETE_ALL_BLOCKS.replace('%1', + String(deleteList.length)))) { + deleteNext(); + } + } + }; + menuOptions.push(deleteOption); + + Blockly.ContextMenu.show(e, menuOptions, this.RTL); +}; + +/** + * Load an audio file. Cache it, ready for instantaneous playing. + * @param {!Array.} filenames List of file types in decreasing order of + * preference (i.e. increasing size). E.g. ['media/go.mp3', 'media/go.wav'] + * Filenames include path from Blockly's root. File extensions matter. + * @param {string} name Name of sound. + * @private + */ +Blockly.WorkspaceSvg.prototype.loadAudio_ = function(filenames, name) { + if (!filenames.length) { + return; + } + try { + var audioTest = new window['Audio'](); + } catch (e) { + // No browser support for Audio. + // IE can throw an error even if the Audio object exists. + return; + } + var sound; + for (var i = 0; i < filenames.length; i++) { + var filename = filenames[i]; + var ext = filename.match(/\.(\w+)$/); + if (ext && audioTest.canPlayType('audio/' + ext[1])) { + // Found an audio format we can play. + sound = new window['Audio'](filename); + break; + } + } + if (sound && sound.play) { + this.SOUNDS_[name] = sound; + } +}; + +/** + * Preload all the audio files so that they play quickly when asked for. + * @private + */ +Blockly.WorkspaceSvg.prototype.preloadAudio_ = function() { + for (var name in this.SOUNDS_) { + var sound = this.SOUNDS_[name]; + sound.volume = .01; + sound.play(); + sound.pause(); + // iOS can only process one sound at a time. Trying to load more than one + // corrupts the earlier ones. Just load one and leave the others uncached. + if (goog.userAgent.IPAD || goog.userAgent.IPHONE) { + break; + } + } +}; + +/** + * Play a named sound at specified volume. If volume is not specified, + * use full volume (1). + * @param {string} name Name of sound. + * @param {number=} opt_volume Volume of sound (0-1). + */ +Blockly.WorkspaceSvg.prototype.playAudio = function(name, opt_volume) { + var sound = this.SOUNDS_[name]; + if (sound) { + // Don't play one sound on top of another. + var now = new Date; + if (now - this.lastSound_ < Blockly.SOUND_LIMIT) { + return; + } + this.lastSound_ = now; + var mySound; + var ie9 = goog.userAgent.DOCUMENT_MODE && + goog.userAgent.DOCUMENT_MODE === 9; + if (ie9 || goog.userAgent.IPAD || goog.userAgent.ANDROID) { + // Creating a new audio node causes lag in IE9, Android and iPad. Android + // and IE9 refetch the file from the server, iPad uses a singleton audio + // node which must be deleted and recreated for each new audio tag. + mySound = sound; + } else { + mySound = sound.cloneNode(); + } + mySound.volume = (opt_volume === undefined ? 1 : opt_volume); + mySound.play(); + } else if (this.options.parentWorkspace) { + // Maybe a workspace on a lower level knows about this sound. + this.options.parentWorkspace.playAudio(name, opt_volume); + } +}; + +/** + * Modify the block tree on the existing toolbox. + * @param {Node|string} tree DOM tree of blocks, or text representation of same. + */ +Blockly.WorkspaceSvg.prototype.updateToolbox = function(tree) { + tree = Blockly.Options.parseToolboxTree(tree); + if (!tree) { + if (this.options.languageTree) { + throw 'Can\'t nullify an existing toolbox.'; + } + return; // No change (null to null). + } + if (!this.options.languageTree) { + throw 'Existing toolbox is null. Can\'t create new toolbox.'; + } + if (tree.getElementsByTagName('category').length) { + if (!this.toolbox_) { + throw 'Existing toolbox has no categories. Can\'t change mode.'; + } + this.options.languageTree = tree; + this.toolbox_.populate_(tree); + this.toolbox_.addColour_(); + } else { + if (!this.flyout_) { + throw 'Existing toolbox has categories. Can\'t change mode.'; + } + this.options.languageTree = tree; + this.flyout_.show(tree.childNodes); + } +}; + +/** + * Mark this workspace as the currently focused main workspace. + */ +Blockly.WorkspaceSvg.prototype.markFocused = function() { + if (this.options.parentWorkspace) { + this.options.parentWorkspace.markFocused(); + } else { + Blockly.mainWorkspace = this; + } +}; + +/** + * Zooming the blocks centered in (x, y) coordinate with zooming in or out. + * @param {number} x X coordinate of center. + * @param {number} y Y coordinate of center. + * @param {number} type Type of zooming (-1 zooming out and 1 zooming in). + */ +Blockly.WorkspaceSvg.prototype.zoom = function(x, y, type) { + var speed = this.options.zoomOptions.scaleSpeed; + var metrics = this.getMetrics(); + var center = this.getParentSvg().createSVGPoint(); + center.x = x; + center.y = y; + center = center.matrixTransform(this.getCanvas().getCTM().inverse()); + x = center.x; + y = center.y; + var canvas = this.getCanvas(); + // Scale factor. + var scaleChange = (type == 1) ? speed : 1 / speed; + // Clamp scale within valid range. + var newScale = this.scale * scaleChange; + if (newScale > this.options.zoomOptions.maxScale) { + scaleChange = this.options.zoomOptions.maxScale / this.scale; + } else if (newScale < this.options.zoomOptions.minScale) { + scaleChange = this.options.zoomOptions.minScale / this.scale; + } + if (this.scale == newScale) { + return; // No change in zoom. + } + if (this.scrollbar) { + var matrix = canvas.getCTM() + .translate(x * (1 - scaleChange), y * (1 - scaleChange)) + .scale(scaleChange); + // newScale and matrix.a should be identical (within a rounding error). + this.scrollX = matrix.e - metrics.absoluteLeft; + this.scrollY = matrix.f - metrics.absoluteTop; + } + this.setScale(newScale); +}; + +/** + * Zooming the blocks centered in the center of view with zooming in or out. + * @param {number} type Type of zooming (-1 zooming out and 1 zooming in). + */ +Blockly.WorkspaceSvg.prototype.zoomCenter = function(type) { + var metrics = this.getMetrics(); + var x = metrics.viewWidth / 2; + var y = metrics.viewHeight / 2; + this.zoom(x, y, type); +}; + +/** + * Zoom the blocks to fit in the workspace if possible. + */ +Blockly.WorkspaceSvg.prototype.zoomToFit = function() { + var metrics = this.getMetrics(); + var blocksBox = this.getBlocksBoundingBox(); + var blocksWidth = blocksBox.width; + var blocksHeight = blocksBox.height; + if (!blocksWidth) { + return; // Prevents zooming to infinity. + } + var workspaceWidth = metrics.viewWidth; + var workspaceHeight = metrics.viewHeight; + if (this.flyout_) { + workspaceWidth -= this.flyout_.width_; + } + if (!this.scrollbar) { + // Orgin point of 0,0 is fixed, blocks will not scroll to center. + blocksWidth += metrics.contentLeft; + blocksHeight += metrics.contentTop; + } + var ratioX = workspaceWidth / blocksWidth; + var ratioY = workspaceHeight / blocksHeight; + this.setScale(Math.min(ratioX, ratioY)); + this.scrollCenter(); +}; + +/** + * Center the workspace. + */ +Blockly.WorkspaceSvg.prototype.scrollCenter = function() { + if (!this.scrollbar) { + // Can't center a non-scrolling workspace. + return; + } + var metrics = this.getMetrics(); + var x = (metrics.contentWidth - metrics.viewWidth) / 2; + if (this.flyout_) { + x -= this.flyout_.width_ / 2; + } + var y = (metrics.contentHeight - metrics.viewHeight) / 2; + this.scrollbar.set(x, y); +}; + +/** + * Set the workspace's zoom factor. + * @param {number} newScale Zoom factor. + */ +Blockly.WorkspaceSvg.prototype.setScale = function(newScale) { + if (this.options.zoomOptions.maxScale && + newScale > this.options.zoomOptions.maxScale) { + newScale = this.options.zoomOptions.maxScale; + } else if (this.options.zoomOptions.minScale && + newScale < this.options.zoomOptions.minScale) { + newScale = this.options.zoomOptions.minScale; + } + this.scale = newScale; + this.updateGridPattern_(); + if (this.scrollbar) { + this.scrollbar.resize(); + } else { + this.translate(this.scrollX, this.scrollY); + } + Blockly.hideChaff(false); + if (this.flyout_) { + // No toolbox, resize flyout. + this.flyout_.reflow(); + } +}; + +/** + * Updates the grid pattern. + * @private + */ +Blockly.WorkspaceSvg.prototype.updateGridPattern_ = function() { + if (!this.options.gridPattern) { + return; // No grid. + } + // MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100. + var safeSpacing = (this.options.gridOptions['spacing'] * this.scale) || 100; + this.options.gridPattern.setAttribute('width', safeSpacing); + this.options.gridPattern.setAttribute('height', safeSpacing); + var half = Math.floor(this.options.gridOptions['spacing'] / 2) + 0.5; + var start = half - this.options.gridOptions['length'] / 2; + var end = half + this.options.gridOptions['length'] / 2; + var line1 = this.options.gridPattern.firstChild; + var line2 = line1 && line1.nextSibling; + half *= this.scale; + start *= this.scale; + end *= this.scale; + if (line1) { + line1.setAttribute('stroke-width', this.scale); + line1.setAttribute('x1', start); + line1.setAttribute('y1', half); + line1.setAttribute('x2', end); + line1.setAttribute('y2', half); + } + if (line2) { + line2.setAttribute('stroke-width', this.scale); + line2.setAttribute('x1', half); + line2.setAttribute('y1', start); + line2.setAttribute('x2', half); + line2.setAttribute('y2', end); + } +}; + + +/** + * Return an object with all the metrics required to size scrollbars for a + * top level workspace. The following properties are computed: + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the content, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .viewLeft: Offset of left edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .contentLeft: Offset of the left-most content from the x=0 coordinate. + * .absoluteTop: Top-edge of view. + * .absoluteLeft: Left-edge of view. + * .toolboxWidth: Width of toolbox, if it exists. Otherwise zero. + * .toolboxHeight: Height of toolbox, if it exists. Otherwise zero. + * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero. + * .flyoutHeight: Height of flyout if it is always open. Otherwise zero. + * .toolboxPosition: Top, bottom, left or right. + * @return {Object} Contains size and position metrics of a top level workspace. + * @private + */ +Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_ = function() { + var svgSize = Blockly.svgSize(this.getParentSvg()); + if (this.toolbox_) { + if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP || + this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { + svgSize.height -= this.toolbox_.getHeight(); + } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT || + this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + svgSize.width -= this.toolbox_.getWidth(); + } + } + // Set the margin to match the flyout's margin so that the workspace does + // not jump as blocks are added. + var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1; + var viewWidth = svgSize.width - MARGIN; + var viewHeight = svgSize.height - MARGIN; + var blockBox = this.getBlocksBoundingBox(); + + // Fix scale. + var contentWidth = blockBox.width * this.scale; + var contentHeight = blockBox.height * this.scale; + var contentX = blockBox.x * this.scale; + var contentY = blockBox.y * this.scale; + if (this.scrollbar) { + // Add a border around the content that is at least half a screenful wide. + // Ensure border is wide enough that blocks can scroll over entire screen. + var leftEdge = Math.min(contentX - viewWidth / 2, + contentX + contentWidth - viewWidth); + var rightEdge = Math.max(contentX + contentWidth + viewWidth / 2, + contentX + viewWidth); + var topEdge = Math.min(contentY - viewHeight / 2, + contentY + contentHeight - viewHeight); + var bottomEdge = Math.max(contentY + contentHeight + viewHeight / 2, + contentY + viewHeight); + } else { + var leftEdge = blockBox.x; + var rightEdge = leftEdge + blockBox.width; + var topEdge = blockBox.y; + var bottomEdge = topEdge + blockBox.height; + } + var absoluteLeft = 0; + if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { + absoluteLeft = this.toolbox_.getWidth(); + } + var absoluteTop = 0; + if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { + absoluteTop = this.toolbox_.getHeight(); + } + + var metrics = { + viewHeight: svgSize.height, + viewWidth: svgSize.width, + contentHeight: bottomEdge - topEdge, + contentWidth: rightEdge - leftEdge, + viewTop: -this.scrollY, + viewLeft: -this.scrollX, + contentTop: topEdge, + contentLeft: leftEdge, + absoluteTop: absoluteTop, + absoluteLeft: absoluteLeft, + toolboxWidth: this.toolbox_ ? this.toolbox_.getWidth() : 0, + toolboxHeight: this.toolbox_ ? this.toolbox_.getHeight() : 0, + flyoutWidth: this.flyout_ ? this.flyout_.getWidth() : 0, + flyoutHeight: this.flyout_ ? this.flyout_.getHeight() : 0, + toolboxPosition: this.toolboxPosition + }; + return metrics; +}; + +/** + * Sets the X/Y translations of a top level workspace to match the scrollbars. + * @param {!Object} xyRatio Contains an x and/or y property which is a float + * between 0 and 1 specifying the degree of scrolling. + * @private + */ +Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) { + if (!this.scrollbar) { + throw 'Attempt to set top level workspace scroll without scrollbars.'; + } + var metrics = this.getMetrics(); + if (goog.isNumber(xyRatio.x)) { + this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft; + } + if (goog.isNumber(xyRatio.y)) { + this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop; + } + var x = this.scrollX + metrics.absoluteLeft; + var y = this.scrollY + metrics.absoluteTop; + this.translate(x, y); + if (this.options.gridPattern) { + this.options.gridPattern.setAttribute('x', x); + this.options.gridPattern.setAttribute('y', y); + if (goog.userAgent.IE) { + // IE doesn't notice that the x/y offsets have changed. Force an update. + this.updateGridPattern_(); + } + } +}; +// Export symbols that would otherwise be renamed by Closure compiler. +Blockly.WorkspaceSvg.prototype['setVisible'] = + Blockly.WorkspaceSvg.prototype.setVisible; diff --git a/src/blockly/core/xml.js b/src/blockly/core/xml.js new file mode 100644 index 0000000..2567560 --- /dev/null +++ b/src/blockly/core/xml.js @@ -0,0 +1,566 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2012 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview XML reader and writer. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Xml'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); + + +/** + * Encode a block tree as XML. + * @param {!Blockly.Workspace} workspace The workspace containing blocks. + * @return {!Element} XML document. + */ +Blockly.Xml.workspaceToDom = function(workspace) { + var xml = goog.dom.createDom('xml'); + var blocks = workspace.getTopBlocks(true); + for (var i = 0, block; block = blocks[i]; i++) { + xml.appendChild(Blockly.Xml.blockToDomWithXY(block)); + } + return xml; +}; + +/** + * Encode a block subtree as XML with XY coordinates. + * @param {!Blockly.Block} block The root block to encode. + * @return {!Element} Tree of XML elements. + */ +Blockly.Xml.blockToDomWithXY = function(block) { + var width; // Not used in LTR. + if (block.workspace.RTL) { + width = block.workspace.getWidth(); + } + var element = Blockly.Xml.blockToDom(block); + var xy = block.getRelativeToSurfaceXY(); + element.setAttribute('x', + Math.round(block.workspace.RTL ? width - xy.x : xy.x)); + element.setAttribute('y', Math.round(xy.y)); + return element; +}; + +/** + * Encode a block subtree as XML. + * @param {!Blockly.Block} block The root block to encode. + * @return {!Element} Tree of XML elements. + */ +Blockly.Xml.blockToDom = function(block) { + var element = goog.dom.createDom(block.isShadow() ? 'shadow' : 'block'); + element.setAttribute('type', block.type); + element.setAttribute('id', block.id); + if (block.mutationToDom) { + // Custom data for an advanced block. + var mutation = block.mutationToDom(); + if (mutation && (mutation.hasChildNodes() || mutation.hasAttributes())) { + element.appendChild(mutation); + } + } + function fieldToDom(field) { + if (field.name && field.EDITABLE) { + var container = goog.dom.createDom('field', null, field.getValue()); + container.setAttribute('name', field.name); + element.appendChild(container); + } + } + for (var i = 0, input; input = block.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + fieldToDom(field); + } + } + + var commentText = block.getCommentText(); + if (commentText) { + var commentElement = goog.dom.createDom('comment', null, commentText); + if (typeof block.comment == 'object') { + commentElement.setAttribute('pinned', block.comment.isVisible()); + var hw = block.comment.getBubbleSize(); + commentElement.setAttribute('h', hw.height); + commentElement.setAttribute('w', hw.width); + } + element.appendChild(commentElement); + } + + if (block.data) { + var dataElement = goog.dom.createDom('data', null, block.data); + element.appendChild(dataElement); + } + + for (var i = 0, input; input = block.inputList[i]; i++) { + var container; + var empty = true; + if (input.type == Blockly.DUMMY_INPUT) { + continue; + } else { + var childBlock = input.connection.targetBlock(); + if (input.type == Blockly.INPUT_VALUE) { + container = goog.dom.createDom('value'); + } else if (input.type == Blockly.NEXT_STATEMENT) { + container = goog.dom.createDom('statement'); + } + var shadow = input.connection.getShadowDom(); + if (shadow && (!childBlock || !childBlock.isShadow())) { + container.appendChild(Blockly.Xml.cloneShadow_(shadow)); + } + if (childBlock) { + container.appendChild(Blockly.Xml.blockToDom(childBlock)); + empty = false; + } + } + container.setAttribute('name', input.name); + if (!empty) { + element.appendChild(container); + } + } + if (block.inputsInlineDefault != block.inputsInline) { + element.setAttribute('inline', block.inputsInline); + } + if (block.isCollapsed()) { + element.setAttribute('collapsed', true); + } + if (block.disabled) { + element.setAttribute('disabled', true); + } + if (!block.isDeletable() && !block.isShadow()) { + element.setAttribute('deletable', false); + } + if (!block.isMovable() && !block.isShadow()) { + element.setAttribute('movable', false); + } + if (!block.isEditable()) { + element.setAttribute('editable', false); + } + + var nextBlock = block.getNextBlock(); + if (nextBlock) { + var container = goog.dom.createDom('next', null, + Blockly.Xml.blockToDom(nextBlock)); + element.appendChild(container); + } + var shadow = block.nextConnection && block.nextConnection.getShadowDom(); + if (shadow && (!nextBlock || !nextBlock.isShadow())) { + container.appendChild(Blockly.Xml.cloneShadow_(shadow)); + } + + return element; +}; + +/** + * Deeply clone the shadow's DOM so that changes don't back-wash to the block. + * @param {!Element} shadow A tree of XML elements. + * @return {!Element} A tree of XML elements. + * @private + */ +Blockly.Xml.cloneShadow_ = function(shadow) { + shadow = shadow.cloneNode(true); + // Walk the tree looking for whitespace. Don't prune whitespace in a tag. + var node = shadow; + var textNode; + while (node) { + if (node.firstChild) { + node = node.firstChild; + } else { + while (node && !node.nextSibling) { + textNode = node; + node = node.parentNode; + if (textNode.nodeType == 3 && textNode.data.trim() == '' && + node.firstChild != textNode) { + // Prune whitespace after a tag. + goog.dom.removeNode(textNode); + } + } + if (node) { + textNode = node; + node = node.nextSibling; + if (textNode.nodeType == 3 && textNode.data.trim() == '') { + // Prune whitespace before a tag. + goog.dom.removeNode(textNode); + } + } + } + } + return shadow; +}; + +/** + * Converts a DOM structure into plain text. + * Currently the text format is fairly ugly: all one line with no whitespace. + * @param {!Element} dom A tree of XML elements. + * @return {string} Text representation. + */ +Blockly.Xml.domToText = function(dom) { + var oSerializer = new XMLSerializer(); + return oSerializer.serializeToString(dom); +}; + +/** + * Converts a DOM structure into properly indented text. + * @param {!Element} dom A tree of XML elements. + * @return {string} Text representation. + */ +Blockly.Xml.domToPrettyText = function(dom) { + // This function is not guaranteed to be correct for all XML. + // But it handles the XML that Blockly generates. + var blob = Blockly.Xml.domToText(dom); + // Place every open and close tag on its own line. + var lines = blob.split('<'); + // Indent every line. + var indent = ''; + for (var i = 1; i < lines.length; i++) { + var line = lines[i]; + if (line[0] == '/') { + indent = indent.substring(2); + } + lines[i] = indent + '<' + line; + if (line[0] != '/' && line.slice(-2) != '/>') { + indent += ' '; + } + } + // Pull simple tags back together. + // E.g. + var text = lines.join('\n'); + text = text.replace(/(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>/g, '$1'); + // Trim leading blank line. + return text.replace(/^\n/, ''); +}; + +/** + * Converts plain text into a DOM structure. + * Throws an error if XML doesn't parse. + * @param {string} text Text representation. + * @return {!Element} A tree of XML elements. + */ +Blockly.Xml.textToDom = function(text) { + var oParser = new DOMParser(); + var dom = oParser.parseFromString(text, 'text/xml'); + // The DOM should have one and only one top-level node, an XML tag. + if (!dom || !dom.firstChild || + dom.firstChild.nodeName.toLowerCase() != 'xml' || + dom.firstChild !== dom.lastChild) { + // Whatever we got back from the parser is not XML. + goog.asserts.fail('Blockly.Xml.textToDom did not obtain a valid XML tree.'); + } + return dom.firstChild; +}; + +/** + * Decode an XML DOM and create blocks on the workspace. + * @param {!Element} xml XML DOM. + * @param {!Blockly.Workspace} workspace The workspace. + */ +Blockly.Xml.domToWorkspace = function(xml, workspace) { + if (xml instanceof Blockly.Workspace) { + var swap = xml; + xml = workspace; + workspace = swap; + console.warn('Deprecated call to Blockly.Xml.domToWorkspace, ' + + 'swap the arguments.'); + } + var width; // Not used in LTR. + if (workspace.RTL) { + width = workspace.getWidth(); + } + Blockly.Field.startCache(); + // Safari 7.1.3 is known to provide node lists with extra references to + // children beyond the lists' length. Trust the length, do not use the + // looping pattern of checking the index for an object. + var childCount = xml.childNodes.length; + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + for (var i = 0; i < childCount; i++) { + var xmlChild = xml.childNodes[i]; + var name = xmlChild.nodeName.toLowerCase(); + if (name == 'block' || + (name == 'shadow' && !Blockly.Events.recordUndo)) { + // Allow top-level shadow blocks if recordUndo is disabled since + // that means an undo is in progress. Such a block is expected + // to be moved to a nested destination in the next operation. + var block = Blockly.Xml.domToBlock(xmlChild, workspace); + var blockX = parseInt(xmlChild.getAttribute('x'), 10); + var blockY = parseInt(xmlChild.getAttribute('y'), 10); + if (!isNaN(blockX) && !isNaN(blockY)) { + block.moveBy(workspace.RTL ? width - blockX : blockX, blockY); + } + } else if (name == 'shadow') { + goog.asserts.fail('Shadow block cannot be a top-level block.'); + } + } + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + Blockly.Field.stopCache(); + + workspace.updateVariableList(false); +}; + +/** + * Decode an XML block tag and create a block (and possibly sub blocks) on the + * workspace. + * @param {!Element} xmlBlock XML block element. + * @param {!Blockly.Workspace} workspace The workspace. + * @return {!Blockly.Block} The root block created. + */ +Blockly.Xml.domToBlock = function(xmlBlock, workspace) { + if (xmlBlock instanceof Blockly.Workspace) { + var swap = xmlBlock; + xmlBlock = workspace; + workspace = swap; + console.warn('Deprecated call to Blockly.Xml.domToBlock, ' + + 'swap the arguments.'); + } + // Create top-level block. + Blockly.Events.disable(); + try { + var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace); + if (workspace.rendered) { + // Hide connections to speed up assembly. + topBlock.setConnectionsHidden(true); + // Generate list of all blocks. + var blocks = topBlock.getDescendants(); + // Render each block. + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].initSvg(); + } + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].render(false); + } + // Populating the connection database may be defered until after the + // blocks have rendered. + setTimeout(function() { + if (topBlock.workspace) { // Check that the block hasn't been deleted. + topBlock.setConnectionsHidden(false); + } + }, 1); + topBlock.updateDisabled(); + // Allow the scrollbars to resize and move based on the new contents. + // TODO(@picklesrus): #387. Remove when domToBlock avoids resizing. + workspace.resizeContents(); + } + } finally { + Blockly.Events.enable(); + } + if (Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.Create(topBlock)); + } + return topBlock; +}; + +/** + * Decode an XML block tag and create a block (and possibly sub blocks) on the + * workspace. + * @param {!Element} xmlBlock XML block element. + * @param {!Blockly.Workspace} workspace The workspace. + * @return {!Blockly.Block} The root block created. + * @private + */ +Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { + var block = null; + var prototypeName = xmlBlock.getAttribute('type'); + goog.asserts.assert(prototypeName, 'Block type unspecified: %s', + xmlBlock.outerHTML); + var id = xmlBlock.getAttribute('id'); + block = workspace.newBlock(prototypeName, id); + + var blockChild = null; + for (var i = 0, xmlChild; xmlChild = xmlBlock.childNodes[i]; i++) { + if (xmlChild.nodeType == 3) { + // Ignore any text at the level. It's all whitespace anyway. + continue; + } + var input; + + // Find any enclosed blocks or shadows in this tag. + var childBlockNode = null; + var childShadowNode = null; + for (var j = 0, grandchildNode; grandchildNode = xmlChild.childNodes[j]; + j++) { + if (grandchildNode.nodeType == 1) { + if (grandchildNode.nodeName.toLowerCase() == 'block') { + childBlockNode = grandchildNode; + } else if (grandchildNode.nodeName.toLowerCase() == 'shadow') { + childShadowNode = grandchildNode; + } + } + } + // Use the shadow block if there is no child block. + if (!childBlockNode && childShadowNode) { + childBlockNode = childShadowNode; + } + + var name = xmlChild.getAttribute('name'); + switch (xmlChild.nodeName.toLowerCase()) { + case 'mutation': + // Custom data for an advanced block. + if (block.domToMutation) { + block.domToMutation(xmlChild); + if (block.initSvg) { + // Mutation may have added some elements that need initalizing. + block.initSvg(); + } + } + break; + case 'comment': + block.setCommentText(xmlChild.textContent); + var visible = xmlChild.getAttribute('pinned'); + if (visible && !block.isInFlyout) { + // Give the renderer a millisecond to render and position the block + // before positioning the comment bubble. + setTimeout(function() { + if (block.comment && block.comment.setVisible) { + block.comment.setVisible(visible == 'true'); + } + }, 1); + } + var bubbleW = parseInt(xmlChild.getAttribute('w'), 10); + var bubbleH = parseInt(xmlChild.getAttribute('h'), 10); + if (!isNaN(bubbleW) && !isNaN(bubbleH) && + block.comment && block.comment.setVisible) { + block.comment.setBubbleSize(bubbleW, bubbleH); + } + break; + case 'data': + block.data = xmlChild.textContent; + break; + case 'title': + // Titles were renamed to field in December 2013. + // Fall through. + case 'field': + var field = block.getField(name); + if (!field) { + console.warn('Ignoring non-existent field ' + name + ' in block ' + + prototypeName); + break; + } + field.setValue(xmlChild.textContent); + break; + case 'value': + case 'statement': + input = block.getInput(name); + if (!input) { + console.warn('Ignoring non-existent input ' + name + ' in block ' + + prototypeName); + break; + } + if (childShadowNode) { + input.connection.setShadowDom(childShadowNode); + } + if (childBlockNode) { + blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode, + workspace); + if (blockChild.outputConnection) { + input.connection.connect(blockChild.outputConnection); + } else if (blockChild.previousConnection) { + input.connection.connect(blockChild.previousConnection); + } else { + goog.asserts.fail( + 'Child block does not have output or previous statement.'); + } + } + break; + case 'next': + if (childShadowNode && block.nextConnection) { + block.nextConnection.setShadowDom(childShadowNode); + } + if (childBlockNode) { + goog.asserts.assert(block.nextConnection, + 'Next statement does not exist.'); + // If there is more than one XML 'next' tag. + goog.asserts.assert(!block.nextConnection.isConnected(), + 'Next statement is already connected.'); + blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode, + workspace); + goog.asserts.assert(blockChild.previousConnection, + 'Next block does not have previous statement.'); + block.nextConnection.connect(blockChild.previousConnection); + } + break; + default: + // Unknown tag; ignore. Same principle as HTML parsers. + console.warn('Ignoring unknown tag: ' + xmlChild.nodeName); + } + } + + var inline = xmlBlock.getAttribute('inline'); + if (inline) { + block.setInputsInline(inline == 'true'); + } + var disabled = xmlBlock.getAttribute('disabled'); + if (disabled) { + block.setDisabled(disabled == 'true'); + } + var deletable = xmlBlock.getAttribute('deletable'); + if (deletable) { + block.setDeletable(deletable == 'true'); + } + var movable = xmlBlock.getAttribute('movable'); + if (movable) { + block.setMovable(movable == 'true'); + } + var editable = xmlBlock.getAttribute('editable'); + if (editable) { + block.setEditable(editable == 'true'); + } + var collapsed = xmlBlock.getAttribute('collapsed'); + if (collapsed) { + block.setCollapsed(collapsed == 'true'); + } + if (xmlBlock.nodeName.toLowerCase() == 'shadow') { + // Ensure all children are also shadows. + var children = block.getChildren(); + for (var i = 0, child; child = children[i]; i++) { + goog.asserts.assert(child.isShadow(), + 'Shadow block not allowed non-shadow child.'); + } + block.setShadow(true); + } + return block; +}; + +/** + * Remove any 'next' block (statements in a stack). + * @param {!Element} xmlBlock XML block element. + */ +Blockly.Xml.deleteNext = function(xmlBlock) { + for (var i = 0, child; child = xmlBlock.childNodes[i]; i++) { + if (child.nodeName.toLowerCase() == 'next') { + xmlBlock.removeChild(child); + break; + } + } +}; + +// Export symbols that would otherwise be renamed by Closure compiler. +if (!goog.global['Blockly']) { + goog.global['Blockly'] = {}; +} +if (!goog.global['Blockly']['Xml']) { + goog.global['Blockly']['Xml'] = {}; +} +goog.global['Blockly']['Xml']['domToText'] = Blockly.Xml.domToText; +goog.global['Blockly']['Xml']['domToWorkspace'] = Blockly.Xml.domToWorkspace; +goog.global['Blockly']['Xml']['textToDom'] = Blockly.Xml.textToDom; +goog.global['Blockly']['Xml']['workspaceToDom'] = Blockly.Xml.workspaceToDom; diff --git a/src/blockly/core/zoom_controls.js b/src/blockly/core/zoom_controls.js new file mode 100644 index 0000000..48d8ce5 --- /dev/null +++ b/src/blockly/core/zoom_controls.js @@ -0,0 +1,239 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2015 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Object representing a zoom icons. + * @author carloslfu@gmail.com (Carlos Galarza) + */ +'use strict'; + +goog.provide('Blockly.ZoomControls'); + +goog.require('goog.dom'); + + +/** + * Class for a zoom controls. + * @param {!Blockly.Workspace} workspace The workspace to sit in. + * @constructor + */ +Blockly.ZoomControls = function(workspace) { + this.workspace_ = workspace; +}; + +/** + * Width of the zoom controls. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.WIDTH_ = 32; + +/** + * Height of the zoom controls. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.HEIGHT_ = 110; + +/** + * Distance between zoom controls and bottom edge of workspace. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.MARGIN_BOTTOM_ = 20; + +/** + * Distance between zoom controls and right edge of workspace. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.MARGIN_SIDE_ = 20; + +/** + * The SVG group containing the zoom controls. + * @type {Element} + * @private + */ +Blockly.ZoomControls.prototype.svgGroup_ = null; + +/** + * Left coordinate of the zoom controls. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.left_ = 0; + +/** + * Top coordinate of the zoom controls. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.top_ = 0; + +/** + * Create the zoom controls. + * @return {!Element} The zoom controls SVG group. + */ +Blockly.ZoomControls.prototype.createDom = function() { + var workspace = this.workspace_; + /* Here's the markup that will be generated: + + + + + + + + + + + + + + + */ + this.svgGroup_ = Blockly.createSvgElement('g', + {'class': 'blocklyZoom'}, null); + var rnd = String(Math.random()).substring(2); + + var clip = Blockly.createSvgElement('clipPath', + {'id': 'blocklyZoomoutClipPath' + rnd}, + this.svgGroup_); + Blockly.createSvgElement('rect', + {'width': 32, 'height': 32, 'y': 77}, + clip); + var zoomoutSvg = Blockly.createSvgElement('image', + {'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, 'x': -64, + 'y': -15, + 'clip-path': 'url(#blocklyZoomoutClipPath' + rnd + ')'}, + this.svgGroup_); + zoomoutSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + workspace.options.pathToMedia + Blockly.SPRITE.url); + + var clip = Blockly.createSvgElement('clipPath', + {'id': 'blocklyZoominClipPath' + rnd}, + this.svgGroup_); + Blockly.createSvgElement('rect', + {'width': 32, 'height': 32, 'y': 43}, + clip); + var zoominSvg = Blockly.createSvgElement('image', + {'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, + 'x': -32, + 'y': -49, + 'clip-path': 'url(#blocklyZoominClipPath' + rnd + ')'}, + this.svgGroup_); + zoominSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + workspace.options.pathToMedia + Blockly.SPRITE.url); + + var clip = Blockly.createSvgElement('clipPath', + {'id': 'blocklyZoomresetClipPath' + rnd}, + this.svgGroup_); + Blockly.createSvgElement('rect', + {'width': 32, 'height': 32}, + clip); + var zoomresetSvg = Blockly.createSvgElement('image', + {'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, 'y': -92, + 'clip-path': 'url(#blocklyZoomresetClipPath' + rnd + ')'}, + this.svgGroup_); + zoomresetSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + workspace.options.pathToMedia + Blockly.SPRITE.url); + + // Attach event listeners. + Blockly.bindEvent_(zoomresetSvg, 'mousedown', null, function(e) { + workspace.setScale(1); + workspace.scrollCenter(); + e.stopPropagation(); // Don't start a workspace scroll. + e.preventDefault(); // Stop double-clicking from selecting text. + }); + Blockly.bindEvent_(zoominSvg, 'mousedown', null, function(e) { + workspace.zoomCenter(1); + e.stopPropagation(); // Don't start a workspace scroll. + e.preventDefault(); // Stop double-clicking from selecting text. + }); + Blockly.bindEvent_(zoomoutSvg, 'mousedown', null, function(e) { + workspace.zoomCenter(-1); + e.stopPropagation(); // Don't start a workspace scroll. + e.preventDefault(); // Stop double-clicking from selecting text. + }); + + return this.svgGroup_; +}; + +/** + * Initialize the zoom controls. + * @param {number} bottom Distance from workspace bottom to bottom of controls. + * @return {number} Distance from workspace bottom to the top of controls. + */ +Blockly.ZoomControls.prototype.init = function(bottom) { + this.bottom_ = this.MARGIN_BOTTOM_ + bottom; + return this.bottom_ + this.HEIGHT_; +}; + +/** + * Dispose of this zoom controls. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.ZoomControls.prototype.dispose = function() { + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.workspace_ = null; +}; + +/** + * Move the zoom controls to the bottom-right corner. + */ +Blockly.ZoomControls.prototype.position = function() { + var metrics = this.workspace_.getMetrics(); + if (!metrics) { + // There are no metrics available (workspace is probably not visible). + return; + } + if (this.workspace_.RTL) { + this.left_ = this.MARGIN_SIDE_ + Blockly.Scrollbar.scrollbarThickness; + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { + this.left_ += metrics.flyoutWidth; + if (this.workspace_.toolbox_) { + this.left_ += metrics.absoluteLeft; + } + } + } else { + this.left_ = metrics.viewWidth + metrics.absoluteLeft - + this.WIDTH_ - this.MARGIN_SIDE_ - Blockly.Scrollbar.scrollbarThickness; + + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + this.left_ -= metrics.flyoutWidth; + } + } + this.top_ = metrics.viewHeight + metrics.absoluteTop - + this.HEIGHT_ - this.bottom_; + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { + this.top_ -= metrics.flyoutHeight; + } + this.svgGroup_.setAttribute('transform', + 'translate(' + this.left_ + ',' + this.top_ + ')'); +}; diff --git a/src/deps.cljs b/src/deps.cljs new file mode 100644 index 0000000..15f1415 --- /dev/null +++ b/src/deps.cljs @@ -0,0 +1,150 @@ +{:foreign-libs [{:file "semantic.js" + :file-min "semantic.min.js" + :provides ["semantic-ui"] + :requires ["cljsjs.jquery"]} +;; generated by generate_deps.py +{:file "blockly/core/warning.js" + :provides ["Blockly.Warning"] + :requires ["Blockly.Bubble" "Blockly.Icon"]} +{:file "blockly/core/field.js" + :provides ["Blockly.Field"] + :requires ["goog.asserts" "goog.dom" "goog.math.Size" "goog.style" "goog.userAgent"]} +{:file "blockly/core/scrollbar.js" + :provides ["Blockly.Scrollbar" "Blockly.ScrollbarPair"] + :requires ["goog.dom" "goog.events"]} +{:file "blockly/core/comment.js" + :provides ["Blockly.Comment"] + :requires ["Blockly.Bubble" "Blockly.Icon" "goog.userAgent"]} +{:file "blockly/core/events.js" + :provides ["Blockly.Events"] + :requires ["goog.math.Coordinate"]} +{:file "blockly/core/trashcan.js" + :provides ["Blockly.Trashcan"] + :requires ["goog.Timer" "goog.dom" "goog.math" "goog.math.Rect"]} +{:file "blockly/core/connection.js" + :provides ["Blockly.Connection"] + :requires ["goog.asserts" "goog.dom"]} +{:file "blockly/core/widgetdiv.js" + :provides ["Blockly.WidgetDiv"] + :requires ["Blockly.Css" "goog.dom" "goog.dom.TagName" "goog.style"]} +{:file "blockly/core/blockly.js" + :provides ["Blockly"] + :requires ["Blockly.BlockSvg.render" "Blockly.Events" "Blockly.FieldAngle" "Blockly.FieldCheckbox" "Blockly.FieldColour" "Blockly.FieldDropdown" "Blockly.FieldImage" "Blockly.FieldTextInput" "Blockly.FieldNumber" "Blockly.FieldVariable" "Blockly.Generator" "Blockly.Msg" "Blockly.Procedures" "Blockly.Toolbox" "Blockly.WidgetDiv" "Blockly.WorkspaceSvg" "Blockly.constants" "Blockly.inject" "Blockly.utils" "goog.color" "goog.userAgent"]} +{:file "blockly/core/flyout_button.js" + :provides ["Blockly.FlyoutButton"] + :requires ["goog.dom" "goog.math.Coordinate"]} +{:file "blockly/core/block_render_svg.js" + :provides ["Blockly.BlockSvg.render"] + :requires ["Blockly.BlockSvg"]} +{:file "blockly/core/utils.js" + :provides ["Blockly.utils"] + :requires ["goog.dom" "goog.events.BrowserFeature" "goog.math.Coordinate" "goog.userAgent"]} +{:file "blockly/core/msg.js" + :provides ["Blockly.Msg"] + :requires []} +{:file "blockly/core/contextmenu.js" + :provides ["Blockly.ContextMenu"] + :requires ["goog.dom" "goog.events" "goog.style" "goog.ui.Menu" "goog.ui.MenuItem"]} +{:file "blockly/core/icon.js" + :provides ["Blockly.Icon"] + :requires ["goog.dom" "goog.math.Coordinate"]} +{:file "blockly/core/field_textinput.js" + :provides ["Blockly.FieldTextInput"] + :requires ["Blockly.Field" "Blockly.Msg" "goog.asserts" "goog.dom" "goog.dom.TagName" "goog.userAgent"]} +{:file "blockly/core/toolbox.js" + :provides ["Blockly.Toolbox"] + :requires ["Blockly.Flyout" "goog.dom" "goog.dom.TagName" "goog.events" "goog.events.BrowserFeature" "goog.html.SafeHtml" "goog.html.SafeStyle" "goog.math.Rect" "goog.style" "goog.ui.tree.TreeControl" "goog.ui.tree.TreeNode"]} +{:file "blockly/core/options.js" + :provides ["Blockly.Options"] + :requires []} +{:file "blockly/core/block.js" + :provides ["Blockly.Block"] + :requires ["Blockly.Blocks" "Blockly.Comment" "Blockly.Connection" "Blockly.Input" "Blockly.Mutator" "Blockly.Warning" "Blockly.Workspace" "Blockly.Xml" "goog.array" "goog.asserts" "goog.math.Coordinate" "goog.string"]} +{:file "blockly/core/block_svg.js" + :provides ["Blockly.BlockSvg"] + :requires ["Blockly.Block" "Blockly.ContextMenu" "Blockly.RenderedConnection" "goog.Timer" "goog.asserts" "goog.dom" "goog.math.Coordinate" "goog.userAgent"]} +{:file "blockly/core/field_dropdown.js" + :provides ["Blockly.FieldDropdown"] + :requires ["Blockly.Field" "goog.dom" "goog.events" "goog.style" "goog.ui.Menu" "goog.ui.MenuItem" "goog.userAgent"]} +{:file "blockly/core/css.js" + :provides ["Blockly.Css"] + :requires []} +{:file "blockly/core/field_checkbox.js" + :provides ["Blockly.FieldCheckbox"] + :requires ["Blockly.Field"]} +{:file "blockly/core/field_label.js" + :provides ["Blockly.FieldLabel"] + :requires ["Blockly.Field" "Blockly.Tooltip" "goog.dom" "goog.math.Size"]} +{:file "blockly/core/names.js" + :provides ["Blockly.Names"] + :requires []} +{:file "blockly/core/mutator.js" + :provides ["Blockly.Mutator"] + :requires ["Blockly.Bubble" "Blockly.Icon" "Blockly.WorkspaceSvg" "goog.Timer" "goog.dom"]} +{:file "blockly/core/constants.js" + :provides ["Blockly.constants"] + :requires []} +{:file "blockly/core/rendered_connection.js" + :provides ["Blockly.RenderedConnection"] + :requires ["Blockly.Connection"]} +{:file "blockly/core/field_colour.js" + :provides ["Blockly.FieldColour"] + :requires ["Blockly.Field" "goog.dom" "goog.events" "goog.style" "goog.ui.ColorPicker"]} +{:file "blockly/core/field_image.js" + :provides ["Blockly.FieldImage"] + :requires ["Blockly.Field" "goog.dom" "goog.math.Size" "goog.userAgent"]} +{:file "blockly/core/field_variable.js" + :provides ["Blockly.FieldVariable"] + :requires ["Blockly.FieldDropdown" "Blockly.Msg" "Blockly.Variables" "goog.string"]} +{:file "blockly/core/input.js" + :provides ["Blockly.Input"] + :requires ["Blockly.Connection" "Blockly.FieldLabel" "goog.asserts"]} +{:file "blockly/core/field_number.js" + :provides ["Blockly.FieldNumber"] + :requires ["Blockly.FieldTextInput" "goog.math"]} +{:file "blockly/core/variables.js" + :provides ["Blockly.Variables"] + :requires ["Blockly.Blocks" "Blockly.Workspace" "goog.string"]} +{:file "blockly/core/workspace_svg.js" + :provides ["Blockly.WorkspaceSvg"] + :requires ["Blockly.ConnectionDB" "Blockly.constants" "Blockly.Options" "Blockly.ScrollbarPair" "Blockly.Trashcan" "Blockly.Workspace" "Blockly.Xml" "Blockly.ZoomControls" "goog.dom" "goog.math.Coordinate" "goog.userAgent"]} +{:file "blockly/core/bubble.js" + :provides ["Blockly.Bubble"] + :requires ["Blockly.Workspace" "goog.dom" "goog.math" "goog.math.Coordinate" "goog.userAgent"]} +{:file "blockly/core/procedures.js" + :provides ["Blockly.Procedures"] + :requires ["Blockly.Blocks" "Blockly.Field" "Blockly.Names" "Blockly.Workspace"]} +{:file "blockly/core/flyout.js" + :provides ["Blockly.Flyout"] + :requires ["Blockly.Block" "Blockly.Comment" "Blockly.Events" "Blockly.FlyoutButton" "Blockly.WorkspaceSvg" "goog.dom" "goog.events" "goog.math.Rect" "goog.userAgent"]} +{:file "blockly/core/xml.js" + :provides ["Blockly.Xml"] + :requires ["goog.asserts" "goog.dom"]} +{:file "blockly/core/blocks.js" + :provides ["Blockly.Blocks"] + :requires []} +{:file "blockly/core/tooltip.js" + :provides ["Blockly.Tooltip"] + :requires ["goog.dom" "goog.dom.TagName"]} +{:file "blockly/core/field_angle.js" + :provides ["Blockly.FieldAngle"] + :requires ["Blockly.FieldTextInput" "goog.math" "goog.userAgent"]} +{:file "blockly/core/zoom_controls.js" + :provides ["Blockly.ZoomControls"] + :requires ["goog.dom"]} +{:file "blockly/core/workspace.js" + :provides ["Blockly.Workspace"] + :requires ["goog.math"]} +{:file "blockly/core/field_date.js" + :provides ["Blockly.FieldDate"] + :requires ["Blockly.Field" "goog.date" "goog.dom" "goog.events" "goog.i18n.DateTimeSymbols" "goog.i18n.DateTimeSymbols_he" "goog.style" "goog.ui.DatePicker"]} +{:file "blockly/core/generator.js" + :provides ["Blockly.Generator"] + :requires ["Blockly.Block" "goog.asserts"]} +{:file "blockly/core/inject.js" + :provides ["Blockly.inject"] + :requires ["Blockly.Css" "Blockly.Options" "Blockly.WorkspaceSvg" "goog.dom" "goog.ui.Component" "goog.userAgent"]} +{:file "blockly/core/connection_db.js" + :provides ["Blockly.ConnectionDB"] + :requires ["Blockly.Connection"]} +]} diff --git a/src/index.cljs.hl b/src/index.cljs.hl index c8be06d..2472649 100644 --- a/src/index.cljs.hl +++ b/src/index.cljs.hl @@ -1,10 +1,36 @@ (page "index.html" - (:require [app.rpc :as rpc])) + (:require [app.rpc :as rpc] + [Blockly])) + +(def toolbox + " + + + + + + + + ") + +(defelem blockly-workspace + [{:keys [options] :as attr} kids] + (let [elem (div (dissoc attr :options) kids)] + (with-init! + (set! (.-workspace elem) + (.inject js/Blockly elem (clj->js options)))) + elem)) + (html (head + (link :rel "stylesheet" :type "text/css" :href "css/semantic.min.css") + (script :src "blocks_compressed.js") + (script :src "en.js") (title "Tankputer")) (body - (h1 "Tankputer"))) + (h1 "Tankputer") + (blockly-workspace :css {:height "480px" :width "600px"} + :options {:media "media/" :toolbox toolbox}))) ;; vim: set expandtab ts=2 sw=2 filetype=clojure : diff --git a/src/semantic.js b/src/semantic.js new file mode 100644 index 0000000..1817530 --- /dev/null +++ b/src/semantic.js @@ -0,0 +1,22500 @@ + /* + * # Semantic UI - 2.2.6 + * https://github.com/Semantic-Org/Semantic-UI + * http://www.semantic-ui.com/ + * + * Copyright 2014 Contributors + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ +/*! + * # Semantic UI 2.2.6 - Site + * http://github.com/semantic-org/semantic-ui/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +;(function ($, window, document, undefined) { + +$.site = $.fn.site = function(parameters) { + var + time = new Date().getTime(), + performance = [], + + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + + settings = ( $.isPlainObject(parameters) ) + ? $.extend(true, {}, $.site.settings, parameters) + : $.extend({}, $.site.settings), + + namespace = settings.namespace, + error = settings.error, + + eventNamespace = '.' + namespace, + moduleNamespace = 'module-' + namespace, + + $document = $(document), + $module = $document, + element = this, + instance = $module.data(moduleNamespace), + + module, + returnedValue + ; + module = { + + initialize: function() { + module.instantiate(); + }, + + instantiate: function() { + module.verbose('Storing instance of site', module); + instance = module; + $module + .data(moduleNamespace, module) + ; + }, + + normalize: function() { + module.fix.console(); + module.fix.requestAnimationFrame(); + }, + + fix: { + console: function() { + module.debug('Normalizing window.console'); + if (console === undefined || console.log === undefined) { + module.verbose('Console not available, normalizing events'); + module.disable.console(); + } + if (typeof console.group == 'undefined' || typeof console.groupEnd == 'undefined' || typeof console.groupCollapsed == 'undefined') { + module.verbose('Console group not available, normalizing events'); + window.console.group = function() {}; + window.console.groupEnd = function() {}; + window.console.groupCollapsed = function() {}; + } + if (typeof console.markTimeline == 'undefined') { + module.verbose('Mark timeline not available, normalizing events'); + window.console.markTimeline = function() {}; + } + }, + consoleClear: function() { + module.debug('Disabling programmatic console clearing'); + window.console.clear = function() {}; + }, + requestAnimationFrame: function() { + module.debug('Normalizing requestAnimationFrame'); + if(window.requestAnimationFrame === undefined) { + module.debug('RequestAnimationFrame not available, normalizing event'); + window.requestAnimationFrame = window.requestAnimationFrame + || window.mozRequestAnimationFrame + || window.webkitRequestAnimationFrame + || window.msRequestAnimationFrame + || function(callback) { setTimeout(callback, 0); } + ; + } + } + }, + + moduleExists: function(name) { + return ($.fn[name] !== undefined && $.fn[name].settings !== undefined); + }, + + enabled: { + modules: function(modules) { + var + enabledModules = [] + ; + modules = modules || settings.modules; + $.each(modules, function(index, name) { + if(module.moduleExists(name)) { + enabledModules.push(name); + } + }); + return enabledModules; + } + }, + + disabled: { + modules: function(modules) { + var + disabledModules = [] + ; + modules = modules || settings.modules; + $.each(modules, function(index, name) { + if(!module.moduleExists(name)) { + disabledModules.push(name); + } + }); + return disabledModules; + } + }, + + change: { + setting: function(setting, value, modules, modifyExisting) { + modules = (typeof modules === 'string') + ? (modules === 'all') + ? settings.modules + : [modules] + : modules || settings.modules + ; + modifyExisting = (modifyExisting !== undefined) + ? modifyExisting + : true + ; + $.each(modules, function(index, name) { + var + namespace = (module.moduleExists(name)) + ? $.fn[name].settings.namespace || false + : true, + $existingModules + ; + if(module.moduleExists(name)) { + module.verbose('Changing default setting', setting, value, name); + $.fn[name].settings[setting] = value; + if(modifyExisting && namespace) { + $existingModules = $(':data(module-' + namespace + ')'); + if($existingModules.length > 0) { + module.verbose('Modifying existing settings', $existingModules); + $existingModules[name]('setting', setting, value); + } + } + } + }); + }, + settings: function(newSettings, modules, modifyExisting) { + modules = (typeof modules === 'string') + ? [modules] + : modules || settings.modules + ; + modifyExisting = (modifyExisting !== undefined) + ? modifyExisting + : true + ; + $.each(modules, function(index, name) { + var + $existingModules + ; + if(module.moduleExists(name)) { + module.verbose('Changing default setting', newSettings, name); + $.extend(true, $.fn[name].settings, newSettings); + if(modifyExisting && namespace) { + $existingModules = $(':data(module-' + namespace + ')'); + if($existingModules.length > 0) { + module.verbose('Modifying existing settings', $existingModules); + $existingModules[name]('setting', newSettings); + } + } + } + }); + } + }, + + enable: { + console: function() { + module.console(true); + }, + debug: function(modules, modifyExisting) { + modules = modules || settings.modules; + module.debug('Enabling debug for modules', modules); + module.change.setting('debug', true, modules, modifyExisting); + }, + verbose: function(modules, modifyExisting) { + modules = modules || settings.modules; + module.debug('Enabling verbose debug for modules', modules); + module.change.setting('verbose', true, modules, modifyExisting); + } + }, + disable: { + console: function() { + module.console(false); + }, + debug: function(modules, modifyExisting) { + modules = modules || settings.modules; + module.debug('Disabling debug for modules', modules); + module.change.setting('debug', false, modules, modifyExisting); + }, + verbose: function(modules, modifyExisting) { + modules = modules || settings.modules; + module.debug('Disabling verbose debug for modules', modules); + module.change.setting('verbose', false, modules, modifyExisting); + } + }, + + console: function(enable) { + if(enable) { + if(instance.cache.console === undefined) { + module.error(error.console); + return; + } + module.debug('Restoring console function'); + window.console = instance.cache.console; + } + else { + module.debug('Disabling console function'); + instance.cache.console = window.console; + window.console = { + clear : function(){}, + error : function(){}, + group : function(){}, + groupCollapsed : function(){}, + groupEnd : function(){}, + info : function(){}, + log : function(){}, + markTimeline : function(){}, + warn : function(){} + }; + } + }, + + destroy: function() { + module.verbose('Destroying previous site for', $module); + $module + .removeData(moduleNamespace) + ; + }, + + cache: {}, + + setting: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value !== undefined) { + settings[name] = value; + } + else { + return settings[name]; + } + }, + internal: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, module, name); + } + else if(value !== undefined) { + module[name] = value; + } + else { + return module[name]; + } + }, + debug: function() { + if(settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.debug.apply(console, arguments); + } + } + }, + verbose: function() { + if(settings.verbose && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.verbose.apply(console, arguments); + } + } + }, + error: function() { + module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); + module.error.apply(console, arguments); + }, + performance: { + log: function(message) { + var + currentTime, + executionTime, + previousTime + ; + if(settings.performance) { + currentTime = new Date().getTime(); + previousTime = time || currentTime; + executionTime = currentTime - previousTime; + time = currentTime; + performance.push({ + 'Element' : element, + 'Name' : message[0], + 'Arguments' : [].slice.call(message, 1) || '', + 'Execution Time' : executionTime + }); + } + clearTimeout(module.performance.timer); + module.performance.timer = setTimeout(module.performance.display, 500); + }, + display: function() { + var + title = settings.name + ':', + totalTime = 0 + ; + time = false; + clearTimeout(module.performance.timer); + $.each(performance, function(index, data) { + totalTime += data['Execution Time']; + }); + title += ' ' + totalTime + 'ms'; + if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { + console.groupCollapsed(title); + if(console.table) { + console.table(performance); + } + else { + $.each(performance, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); + }); + } + console.groupEnd(); + } + performance = []; + } + }, + invoke: function(query, passedArguments, context) { + var + object = instance, + maxDepth, + found, + response + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + if(typeof query == 'string' && object !== undefined) { + query = query.split(/[\. ]/); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + var camelCaseValue = (depth != maxDepth) + ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) + : query + ; + if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { + object = object[camelCaseValue]; + } + else if( object[camelCaseValue] !== undefined ) { + found = object[camelCaseValue]; + return false; + } + else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { + object = object[value]; + } + else if( object[value] !== undefined ) { + found = object[value]; + return false; + } + else { + module.error(error.method, query); + return false; + } + }); + } + if ( $.isFunction( found ) ) { + response = found.apply(context, passedArguments); + } + else if(found !== undefined) { + response = found; + } + if($.isArray(returnedValue)) { + returnedValue.push(response); + } + else if(returnedValue !== undefined) { + returnedValue = [returnedValue, response]; + } + else if(response !== undefined) { + returnedValue = response; + } + return found; + } + }; + + if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } + module.invoke(query); + } + else { + if(instance !== undefined) { + module.destroy(); + } + module.initialize(); + } + return (returnedValue !== undefined) + ? returnedValue + : this + ; +}; + +$.site.settings = { + + name : 'Site', + namespace : 'site', + + error : { + console : 'Console cannot be restored, most likely it was overwritten outside of module', + method : 'The method you called is not defined.' + }, + + debug : false, + verbose : false, + performance : true, + + modules: [ + 'accordion', + 'api', + 'checkbox', + 'dimmer', + 'dropdown', + 'embed', + 'form', + 'modal', + 'nag', + 'popup', + 'rating', + 'shape', + 'sidebar', + 'state', + 'sticky', + 'tab', + 'transition', + 'visit', + 'visibility' + ], + + siteNamespace : 'site', + namespaceStub : { + cache : {}, + config : {}, + sections : {}, + section : {}, + utilities : {} + } + +}; + +// allows for selection of elements with data attributes +$.extend($.expr[ ":" ], { + data: ($.expr.createPseudo) + ? $.expr.createPseudo(function(dataName) { + return function(elem) { + return !!$.data(elem, dataName); + }; + }) + : function(elem, i, match) { + // support: jQuery < 1.8 + return !!$.data(elem, match[ 3 ]); + } +}); + + +})( jQuery, window, document ); + +/*! + * # Semantic UI 2.2.6 - Form Validation + * http://github.com/semantic-org/semantic-ui/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +;(function ($, window, document, undefined) { + +"use strict"; + +window = (typeof window != 'undefined' && window.Math == Math) + ? window + : (typeof self != 'undefined' && self.Math == Math) + ? self + : Function('return this')() +; + +$.fn.form = function(parameters) { + var + $allModules = $(this), + moduleSelector = $allModules.selector || '', + + time = new Date().getTime(), + performance = [], + + query = arguments[0], + legacyParameters = arguments[1], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + returnedValue + ; + $allModules + .each(function() { + var + $module = $(this), + element = this, + + formErrors = [], + keyHeldDown = false, + + // set at run-time + $field, + $group, + $message, + $prompt, + $submit, + $clear, + $reset, + + settings, + validation, + + metadata, + selector, + className, + error, + + namespace, + moduleNamespace, + eventNamespace, + + instance, + module + ; + + module = { + + initialize: function() { + + // settings grabbed at run time + module.get.settings(); + if(methodInvoked) { + if(instance === undefined) { + module.instantiate(); + } + module.invoke(query); + } + else { + if(instance !== undefined) { + instance.invoke('destroy'); + } + module.verbose('Initializing form validation', $module, settings); + module.bindEvents(); + module.set.defaults(); + module.instantiate(); + } + }, + + instantiate: function() { + module.verbose('Storing instance of module', module); + instance = module; + $module + .data(moduleNamespace, module) + ; + }, + + destroy: function() { + module.verbose('Destroying previous module', instance); + module.removeEvents(); + $module + .removeData(moduleNamespace) + ; + }, + + refresh: function() { + module.verbose('Refreshing selector cache'); + $field = $module.find(selector.field); + $group = $module.find(selector.group); + $message = $module.find(selector.message); + $prompt = $module.find(selector.prompt); + + $submit = $module.find(selector.submit); + $clear = $module.find(selector.clear); + $reset = $module.find(selector.reset); + }, + + submit: function() { + module.verbose('Submitting form', $module); + $module + .submit() + ; + }, + + attachEvents: function(selector, action) { + action = action || 'submit'; + $(selector) + .on('click' + eventNamespace, function(event) { + module[action](); + event.preventDefault(); + }) + ; + }, + + bindEvents: function() { + module.verbose('Attaching form events'); + $module + .on('submit' + eventNamespace, module.validate.form) + .on('blur' + eventNamespace, selector.field, module.event.field.blur) + .on('click' + eventNamespace, selector.submit, module.submit) + .on('click' + eventNamespace, selector.reset, module.reset) + .on('click' + eventNamespace, selector.clear, module.clear) + ; + if(settings.keyboardShortcuts) { + $module + .on('keydown' + eventNamespace, selector.field, module.event.field.keydown) + ; + } + $field + .each(function() { + var + $input = $(this), + type = $input.prop('type'), + inputEvent = module.get.changeEvent(type, $input) + ; + $(this) + .on(inputEvent + eventNamespace, module.event.field.change) + ; + }) + ; + }, + + clear: function() { + $field + .each(function () { + var + $field = $(this), + $element = $field.parent(), + $fieldGroup = $field.closest($group), + $prompt = $fieldGroup.find(selector.prompt), + defaultValue = $field.data(metadata.defaultValue) || '', + isCheckbox = $element.is(selector.uiCheckbox), + isDropdown = $element.is(selector.uiDropdown), + isErrored = $fieldGroup.hasClass(className.error) + ; + if(isErrored) { + module.verbose('Resetting error on field', $fieldGroup); + $fieldGroup.removeClass(className.error); + $prompt.remove(); + } + if(isDropdown) { + module.verbose('Resetting dropdown value', $element, defaultValue); + $element.dropdown('clear'); + } + else if(isCheckbox) { + $field.prop('checked', false); + } + else { + module.verbose('Resetting field value', $field, defaultValue); + $field.val(''); + } + }) + ; + }, + + reset: function() { + $field + .each(function () { + var + $field = $(this), + $element = $field.parent(), + $fieldGroup = $field.closest($group), + $prompt = $fieldGroup.find(selector.prompt), + defaultValue = $field.data(metadata.defaultValue), + isCheckbox = $element.is(selector.uiCheckbox), + isDropdown = $element.is(selector.uiDropdown), + isErrored = $fieldGroup.hasClass(className.error) + ; + if(defaultValue === undefined) { + return; + } + if(isErrored) { + module.verbose('Resetting error on field', $fieldGroup); + $fieldGroup.removeClass(className.error); + $prompt.remove(); + } + if(isDropdown) { + module.verbose('Resetting dropdown value', $element, defaultValue); + $element.dropdown('restore defaults'); + } + else if(isCheckbox) { + module.verbose('Resetting checkbox value', $element, defaultValue); + $field.prop('checked', defaultValue); + } + else { + module.verbose('Resetting field value', $field, defaultValue); + $field.val(defaultValue); + } + }) + ; + }, + + is: { + bracketedRule: function(rule) { + return (rule.type && rule.type.match(settings.regExp.bracket)); + }, + empty: function($field) { + if(!$field || $field.length === 0) { + return true; + } + else if($field.is('input[type="checkbox"]')) { + return !$field.is(':checked'); + } + else { + return module.is.blank($field); + } + }, + blank: function($field) { + return $.trim($field.val()) === ''; + }, + valid: function() { + var + allValid = true + ; + module.verbose('Checking if form is valid'); + $.each(validation, function(fieldName, field) { + if( !( module.validate.field(field, fieldName) ) ) { + allValid = false; + } + }); + return allValid; + } + }, + + removeEvents: function() { + $module + .off(eventNamespace) + ; + $field + .off(eventNamespace) + ; + $submit + .off(eventNamespace) + ; + $field + .off(eventNamespace) + ; + }, + + event: { + field: { + keydown: function(event) { + var + $field = $(this), + key = event.which, + isInput = $field.is(selector.input), + isCheckbox = $field.is(selector.checkbox), + isInDropdown = ($field.closest(selector.uiDropdown).length > 0), + keyCode = { + enter : 13, + escape : 27 + } + ; + if( key == keyCode.escape) { + module.verbose('Escape key pressed blurring field'); + $field + .blur() + ; + } + if(!event.ctrlKey && key == keyCode.enter && isInput && !isInDropdown && !isCheckbox) { + if(!keyHeldDown) { + $field + .one('keyup' + eventNamespace, module.event.field.keyup) + ; + module.submit(); + module.debug('Enter pressed on input submitting form'); + } + keyHeldDown = true; + } + }, + keyup: function() { + keyHeldDown = false; + }, + blur: function(event) { + var + $field = $(this), + $fieldGroup = $field.closest($group), + validationRules = module.get.validation($field) + ; + if( $fieldGroup.hasClass(className.error) ) { + module.debug('Revalidating field', $field, validationRules); + if(validationRules) { + module.validate.field( validationRules ); + } + } + else if(settings.on == 'blur' || settings.on == 'change') { + if(validationRules) { + module.validate.field( validationRules ); + } + } + }, + change: function(event) { + var + $field = $(this), + $fieldGroup = $field.closest($group), + validationRules = module.get.validation($field) + ; + if(settings.on == 'change' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) ) { + clearTimeout(module.timer); + module.timer = setTimeout(function() { + module.debug('Revalidating field', $field, module.get.validation($field)); + module.validate.field( validationRules ); + }, settings.delay); + } + } + } + + }, + + get: { + ancillaryValue: function(rule) { + if(!rule.type || (!rule.value && !module.is.bracketedRule(rule))) { + return false; + } + return (rule.value !== undefined) + ? rule.value + : rule.type.match(settings.regExp.bracket)[1] + '' + ; + }, + ruleName: function(rule) { + if( module.is.bracketedRule(rule) ) { + return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], ''); + } + return rule.type; + }, + changeEvent: function(type, $input) { + if(type == 'checkbox' || type == 'radio' || type == 'hidden' || $input.is('select')) { + return 'change'; + } + else { + return module.get.inputEvent(); + } + }, + inputEvent: function() { + return (document.createElement('input').oninput !== undefined) + ? 'input' + : (document.createElement('input').onpropertychange !== undefined) + ? 'propertychange' + : 'keyup' + ; + }, + prompt: function(rule, field) { + var + ruleName = module.get.ruleName(rule), + ancillary = module.get.ancillaryValue(rule), + prompt = rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule, + requiresValue = (prompt.search('{value}') !== -1), + requiresName = (prompt.search('{name}') !== -1), + $label, + $field, + name + ; + if(requiresName || requiresValue) { + $field = module.get.field(field.identifier); + } + if(requiresValue) { + prompt = prompt.replace('{value}', $field.val()); + } + if(requiresName) { + $label = $field.closest(selector.group).find('label').eq(0); + name = ($label.length == 1) + ? $label.text() + : $field.prop('placeholder') || settings.text.unspecifiedField + ; + prompt = prompt.replace('{name}', name); + } + prompt = prompt.replace('{identifier}', field.identifier); + prompt = prompt.replace('{ruleValue}', ancillary); + if(!rule.prompt) { + module.verbose('Using default validation prompt for type', prompt, ruleName); + } + return prompt; + }, + settings: function() { + if($.isPlainObject(parameters)) { + var + keys = Object.keys(parameters), + isLegacySettings = (keys.length > 0) + ? (parameters[keys[0]].identifier !== undefined && parameters[keys[0]].rules !== undefined) + : false, + ruleKeys + ; + if(isLegacySettings) { + // 1.x (ducktyped) + settings = $.extend(true, {}, $.fn.form.settings, legacyParameters); + validation = $.extend({}, $.fn.form.settings.defaults, parameters); + module.error(settings.error.oldSyntax, element); + module.verbose('Extending settings from legacy parameters', validation, settings); + } + else { + // 2.x + if(parameters.fields) { + ruleKeys = Object.keys(parameters.fields); + if( typeof parameters.fields[ruleKeys[0]] == 'string' || $.isArray(parameters.fields[ruleKeys[0]]) ) { + $.each(parameters.fields, function(name, rules) { + if(typeof rules == 'string') { + rules = [rules]; + } + parameters.fields[name] = { + rules: [] + }; + $.each(rules, function(index, rule) { + parameters.fields[name].rules.push({ type: rule }); + }); + }); + } + } + + settings = $.extend(true, {}, $.fn.form.settings, parameters); + validation = $.extend({}, $.fn.form.settings.defaults, settings.fields); + module.verbose('Extending settings', validation, settings); + } + } + else { + settings = $.fn.form.settings; + validation = $.fn.form.settings.defaults; + module.verbose('Using default form validation', validation, settings); + } + + // shorthand + namespace = settings.namespace; + metadata = settings.metadata; + selector = settings.selector; + className = settings.className; + error = settings.error; + moduleNamespace = 'module-' + namespace; + eventNamespace = '.' + namespace; + + // grab instance + instance = $module.data(moduleNamespace); + + // refresh selector cache + module.refresh(); + }, + field: function(identifier) { + module.verbose('Finding field with identifier', identifier); + if( $field.filter('#' + identifier).length > 0 ) { + return $field.filter('#' + identifier); + } + else if( $field.filter('[name="' + identifier +'"]').length > 0 ) { + return $field.filter('[name="' + identifier +'"]'); + } + else if( $field.filter('[name="' + identifier +'[]"]').length > 0 ) { + return $field.filter('[name="' + identifier +'[]"]'); + } + else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) { + return $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]'); + } + return $(''); + }, + fields: function(fields) { + var + $fields = $() + ; + $.each(fields, function(index, name) { + $fields = $fields.add( module.get.field(name) ); + }); + return $fields; + }, + validation: function($field) { + var + fieldValidation, + identifier + ; + if(!validation) { + return false; + } + $.each(validation, function(fieldName, field) { + identifier = field.identifier || fieldName; + if( module.get.field(identifier)[0] == $field[0] ) { + field.identifier = identifier; + fieldValidation = field; + } + }); + return fieldValidation || false; + }, + value: function (field) { + var + fields = [], + results + ; + fields.push(field); + results = module.get.values.call(element, fields); + return results[field]; + }, + values: function (fields) { + var + $fields = $.isArray(fields) + ? module.get.fields(fields) + : $field, + values = {} + ; + $fields.each(function(index, field) { + var + $field = $(field), + type = $field.prop('type'), + name = $field.prop('name'), + value = $field.val(), + isCheckbox = $field.is(selector.checkbox), + isRadio = $field.is(selector.radio), + isMultiple = (name.indexOf('[]') !== -1), + isChecked = (isCheckbox) + ? $field.is(':checked') + : false + ; + if(name) { + if(isMultiple) { + name = name.replace('[]', ''); + if(!values[name]) { + values[name] = []; + } + if(isCheckbox) { + if(isChecked) { + values[name].push(value || true); + } + else { + values[name].push(false); + } + } + else { + values[name].push(value); + } + } + else { + if(isRadio) { + if(isChecked) { + values[name] = value; + } + } + else if(isCheckbox) { + if(isChecked) { + values[name] = value || true; + } + else { + values[name] = false; + } + } + else { + values[name] = value; + } + } + } + }); + return values; + } + }, + + has: { + + field: function(identifier) { + module.verbose('Checking for existence of a field with identifier', identifier); + if(typeof identifier !== 'string') { + module.error(error.identifier, identifier); + } + if( $field.filter('#' + identifier).length > 0 ) { + return true; + } + else if( $field.filter('[name="' + identifier +'"]').length > 0 ) { + return true; + } + else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) { + return true; + } + return false; + } + + }, + + add: { + prompt: function(identifier, errors) { + var + $field = module.get.field(identifier), + $fieldGroup = $field.closest($group), + $prompt = $fieldGroup.children(selector.prompt), + promptExists = ($prompt.length !== 0) + ; + errors = (typeof errors == 'string') + ? [errors] + : errors + ; + module.verbose('Adding field error state', identifier); + $fieldGroup + .addClass(className.error) + ; + if(settings.inline) { + if(!promptExists) { + $prompt = settings.templates.prompt(errors); + $prompt + .appendTo($fieldGroup) + ; + } + $prompt + .html(errors[0]) + ; + if(!promptExists) { + if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { + module.verbose('Displaying error with css transition', settings.transition); + $prompt.transition(settings.transition + ' in', settings.duration); + } + else { + module.verbose('Displaying error with fallback javascript animation'); + $prompt + .fadeIn(settings.duration) + ; + } + } + else { + module.verbose('Inline errors are disabled, no inline error added', identifier); + } + } + }, + errors: function(errors) { + module.debug('Adding form error messages', errors); + module.set.error(); + $message + .html( settings.templates.error(errors) ) + ; + } + }, + + remove: { + prompt: function(identifier) { + var + $field = module.get.field(identifier), + $fieldGroup = $field.closest($group), + $prompt = $fieldGroup.children(selector.prompt) + ; + $fieldGroup + .removeClass(className.error) + ; + if(settings.inline && $prompt.is(':visible')) { + module.verbose('Removing prompt for field', identifier); + if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { + $prompt.transition(settings.transition + ' out', settings.duration, function() { + $prompt.remove(); + }); + } + else { + $prompt + .fadeOut(settings.duration, function(){ + $prompt.remove(); + }) + ; + } + } + } + }, + + set: { + success: function() { + $module + .removeClass(className.error) + .addClass(className.success) + ; + }, + defaults: function () { + $field + .each(function () { + var + $field = $(this), + isCheckbox = ($field.filter(selector.checkbox).length > 0), + value = (isCheckbox) + ? $field.is(':checked') + : $field.val() + ; + $field.data(metadata.defaultValue, value); + }) + ; + }, + error: function() { + $module + .removeClass(className.success) + .addClass(className.error) + ; + }, + value: function (field, value) { + var + fields = {} + ; + fields[field] = value; + return module.set.values.call(element, fields); + }, + values: function (fields) { + if($.isEmptyObject(fields)) { + return; + } + $.each(fields, function(key, value) { + var + $field = module.get.field(key), + $element = $field.parent(), + isMultiple = $.isArray(value), + isCheckbox = $element.is(selector.uiCheckbox), + isDropdown = $element.is(selector.uiDropdown), + isRadio = ($field.is(selector.radio) && isCheckbox), + fieldExists = ($field.length > 0), + $multipleField + ; + if(fieldExists) { + if(isMultiple && isCheckbox) { + module.verbose('Selecting multiple', value, $field); + $element.checkbox('uncheck'); + $.each(value, function(index, value) { + $multipleField = $field.filter('[value="' + value + '"]'); + $element = $multipleField.parent(); + if($multipleField.length > 0) { + $element.checkbox('check'); + } + }); + } + else if(isRadio) { + module.verbose('Selecting radio value', value, $field); + $field.filter('[value="' + value + '"]') + .parent(selector.uiCheckbox) + .checkbox('check') + ; + } + else if(isCheckbox) { + module.verbose('Setting checkbox value', value, $element); + if(value === true) { + $element.checkbox('check'); + } + else { + $element.checkbox('uncheck'); + } + } + else if(isDropdown) { + module.verbose('Setting dropdown value', value, $element); + $element.dropdown('set selected', value); + } + else { + module.verbose('Setting field value', value, $field); + $field.val(value); + } + } + }); + } + }, + + validate: { + + form: function(event, ignoreCallbacks) { + var + values = module.get.values(), + apiRequest + ; + + // input keydown event will fire submit repeatedly by browser default + if(keyHeldDown) { + return false; + } + + // reset errors + formErrors = []; + if( module.is.valid() ) { + module.debug('Form has no validation errors, submitting'); + module.set.success(); + if(ignoreCallbacks !== true) { + return settings.onSuccess.call(element, event, values); + } + } + else { + module.debug('Form has errors'); + module.set.error(); + if(!settings.inline) { + module.add.errors(formErrors); + } + // prevent ajax submit + if($module.data('moduleApi') !== undefined) { + event.stopImmediatePropagation(); + } + if(ignoreCallbacks !== true) { + return settings.onFailure.call(element, formErrors, values); + } + } + }, + + // takes a validation object and returns whether field passes validation + field: function(field, fieldName) { + var + identifier = field.identifier || fieldName, + $field = module.get.field(identifier), + $dependsField = (field.depends) + ? module.get.field(field.depends) + : false, + fieldValid = true, + fieldErrors = [] + ; + if(!field.identifier) { + module.debug('Using field name as identifier', identifier); + field.identifier = identifier; + } + if($field.prop('disabled')) { + module.debug('Field is disabled. Skipping', identifier); + fieldValid = true; + } + else if(field.optional && module.is.blank($field)){ + module.debug('Field is optional and blank. Skipping', identifier); + fieldValid = true; + } + else if(field.depends && module.is.empty($dependsField)) { + module.debug('Field depends on another value that is not present or empty. Skipping', $dependsField); + fieldValid = true; + } + else if(field.rules !== undefined) { + $.each(field.rules, function(index, rule) { + if( module.has.field(identifier) && !( module.validate.rule(field, rule) ) ) { + module.debug('Field is invalid', identifier, rule.type); + fieldErrors.push(module.get.prompt(rule, field)); + fieldValid = false; + } + }); + } + if(fieldValid) { + module.remove.prompt(identifier, fieldErrors); + settings.onValid.call($field); + } + else { + formErrors = formErrors.concat(fieldErrors); + module.add.prompt(identifier, fieldErrors); + settings.onInvalid.call($field, fieldErrors); + return false; + } + return true; + }, + + // takes validation rule and returns whether field passes rule + rule: function(field, rule) { + var + $field = module.get.field(field.identifier), + type = rule.type, + value = $field.val(), + isValid = true, + ancillary = module.get.ancillaryValue(rule), + ruleName = module.get.ruleName(rule), + ruleFunction = settings.rules[ruleName] + ; + if( !$.isFunction(ruleFunction) ) { + module.error(error.noRule, ruleName); + return; + } + // cast to string avoiding encoding special values + value = (value === undefined || value === '' || value === null) + ? '' + : $.trim(value + '') + ; + return ruleFunction.call($field, value, ancillary); + } + }, + + setting: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value !== undefined) { + settings[name] = value; + } + else { + return settings[name]; + } + }, + internal: function(name, value) { + if( $.isPlainObject(name) ) { + $.extend(true, module, name); + } + else if(value !== undefined) { + module[name] = value; + } + else { + return module[name]; + } + }, + debug: function() { + if(!settings.silent && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.debug.apply(console, arguments); + } + } + }, + verbose: function() { + if(!settings.silent && settings.verbose && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.verbose.apply(console, arguments); + } + } + }, + error: function() { + if(!settings.silent) { + module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); + module.error.apply(console, arguments); + } + }, + performance: { + log: function(message) { + var + currentTime, + executionTime, + previousTime + ; + if(settings.performance) { + currentTime = new Date().getTime(); + previousTime = time || currentTime; + executionTime = currentTime - previousTime; + time = currentTime; + performance.push({ + 'Name' : message[0], + 'Arguments' : [].slice.call(message, 1) || '', + 'Element' : element, + 'Execution Time' : executionTime + }); + } + clearTimeout(module.performance.timer); + module.performance.timer = setTimeout(module.performance.display, 500); + }, + display: function() { + var + title = settings.name + ':', + totalTime = 0 + ; + time = false; + clearTimeout(module.performance.timer); + $.each(performance, function(index, data) { + totalTime += data['Execution Time']; + }); + title += ' ' + totalTime + 'ms'; + if(moduleSelector) { + title += ' \'' + moduleSelector + '\''; + } + if($allModules.length > 1) { + title += ' ' + '(' + $allModules.length + ')'; + } + if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { + console.groupCollapsed(title); + if(console.table) { + console.table(performance); + } + else { + $.each(performance, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); + }); + } + console.groupEnd(); + } + performance = []; + } + }, + invoke: function(query, passedArguments, context) { + var + object = instance, + maxDepth, + found, + response + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + if(typeof query == 'string' && object !== undefined) { + query = query.split(/[\. ]/); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + var camelCaseValue = (depth != maxDepth) + ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) + : query + ; + if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { + object = object[camelCaseValue]; + } + else if( object[camelCaseValue] !== undefined ) { + found = object[camelCaseValue]; + return false; + } + else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { + object = object[value]; + } + else if( object[value] !== undefined ) { + found = object[value]; + return false; + } + else { + return false; + } + }); + } + if( $.isFunction( found ) ) { + response = found.apply(context, passedArguments); + } + else if(found !== undefined) { + response = found; + } + if($.isArray(returnedValue)) { + returnedValue.push(response); + } + else if(returnedValue !== undefined) { + returnedValue = [returnedValue, response]; + } + else if(response !== undefined) { + returnedValue = response; + } + return found; + } + }; + module.initialize(); + }) + ; + + return (returnedValue !== undefined) + ? returnedValue + : this + ; +}; + +$.fn.form.settings = { + + name : 'Form', + namespace : 'form', + + debug : false, + verbose : false, + performance : true, + + fields : false, + + keyboardShortcuts : true, + on : 'submit', + inline : false, + + delay : 200, + revalidate : true, + + transition : 'scale', + duration : 200, + + onValid : function() {}, + onInvalid : function() {}, + onSuccess : function() { return true; }, + onFailure : function() { return false; }, + + metadata : { + defaultValue : 'default', + validate : 'validate' + }, + + regExp: { + bracket : /\[(.*)\]/i, + decimal : /^\d*(\.)\d+/, + email : /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i, + escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, + flags : /^\/(.*)\/(.*)?/, + integer : /^\-?\d+$/, + number : /^\-?\d*(\.\d+)?$/, + url : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i + }, + + text: { + unspecifiedRule : 'Please enter a valid value', + unspecifiedField : 'This field' + }, + + prompt: { + empty : '{name} must have a value', + checked : '{name} must be checked', + email : '{name} must be a valid e-mail', + url : '{name} must be a valid url', + regExp : '{name} is not formatted correctly', + integer : '{name} must be an integer', + decimal : '{name} must be a decimal number', + number : '{name} must be set to a number', + is : '{name} must be "{ruleValue}"', + isExactly : '{name} must be exactly "{ruleValue}"', + not : '{name} cannot be set to "{ruleValue}"', + notExactly : '{name} cannot be set to exactly "{ruleValue}"', + contain : '{name} cannot contain "{ruleValue}"', + containExactly : '{name} cannot contain exactly "{ruleValue}"', + doesntContain : '{name} must contain "{ruleValue}"', + doesntContainExactly : '{name} must contain exactly "{ruleValue}"', + minLength : '{name} must be at least {ruleValue} characters', + length : '{name} must be at least {ruleValue} characters', + exactLength : '{name} must be exactly {ruleValue} characters', + maxLength : '{name} cannot be longer than {ruleValue} characters', + match : '{name} must match {ruleValue} field', + different : '{name} must have a different value than {ruleValue} field', + creditCard : '{name} must be a valid credit card number', + minCount : '{name} must have at least {ruleValue} choices', + exactCount : '{name} must have exactly {ruleValue} choices', + maxCount : '{name} must have {ruleValue} or less choices' + }, + + selector : { + checkbox : 'input[type="checkbox"], input[type="radio"]', + clear : '.clear', + field : 'input, textarea, select', + group : '.field', + input : 'input', + message : '.error.message', + prompt : '.prompt.label', + radio : 'input[type="radio"]', + reset : '.reset:not([type="reset"])', + submit : '.submit:not([type="submit"])', + uiCheckbox : '.ui.checkbox', + uiDropdown : '.ui.dropdown' + }, + + className : { + error : 'error', + label : 'ui prompt label', + pressed : 'down', + success : 'success' + }, + + error: { + identifier : 'You must specify a string identifier for each field', + method : 'The method you called is not defined.', + noRule : 'There is no rule matching the one you specified', + oldSyntax : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.' + }, + + templates: { + + // template that produces error message + error: function(errors) { + var + html = '
    ' + ; + $.each(errors, function(index, value) { + html += '
  • ' + value + '
  • '; + }); + html += '
'; + return $(html); + }, + + // template that produces label + prompt: function(errors) { + return $('
') + .addClass('ui basic red pointing prompt label') + .html(errors[0]) + ; + } + }, + + rules: { + + // is not empty or blank string + empty: function(value) { + return !(value === undefined || '' === value || $.isArray(value) && value.length === 0); + }, + + // checkbox checked + checked: function() { + return ($(this).filter(':checked').length > 0); + }, + + // is most likely an email + email: function(value){ + return $.fn.form.settings.regExp.email.test(value); + }, + + // value is most likely url + url: function(value) { + return $.fn.form.settings.regExp.url.test(value); + }, + + // matches specified regExp + regExp: function(value, regExp) { + if(regExp instanceof RegExp) { + return value.match(regExp); + } + var + regExpParts = regExp.match($.fn.form.settings.regExp.flags), + flags + ; + // regular expression specified as /baz/gi (flags) + if(regExpParts) { + regExp = (regExpParts.length >= 2) + ? regExpParts[1] + : regExp + ; + flags = (regExpParts.length >= 3) + ? regExpParts[2] + : '' + ; + } + return value.match( new RegExp(regExp, flags) ); + }, + + // is valid integer or matches range + integer: function(value, range) { + var + intRegExp = $.fn.form.settings.regExp.integer, + min, + max, + parts + ; + if( !range || ['', '..'].indexOf(range) !== -1) { + // do nothing + } + else if(range.indexOf('..') == -1) { + if(intRegExp.test(range)) { + min = max = range - 0; + } + } + else { + parts = range.split('..', 2); + if(intRegExp.test(parts[0])) { + min = parts[0] - 0; + } + if(intRegExp.test(parts[1])) { + max = parts[1] - 0; + } + } + return ( + intRegExp.test(value) && + (min === undefined || value >= min) && + (max === undefined || value <= max) + ); + }, + + // is valid number (with decimal) + decimal: function(value) { + return $.fn.form.settings.regExp.decimal.test(value); + }, + + // is valid number + number: function(value) { + return $.fn.form.settings.regExp.number.test(value); + }, + + // is value (case insensitive) + is: function(value, text) { + text = (typeof text == 'string') + ? text.toLowerCase() + : text + ; + value = (typeof value == 'string') + ? value.toLowerCase() + : value + ; + return (value == text); + }, + + // is value + isExactly: function(value, text) { + return (value == text); + }, + + // value is not another value (case insensitive) + not: function(value, notValue) { + value = (typeof value == 'string') + ? value.toLowerCase() + : value + ; + notValue = (typeof notValue == 'string') + ? notValue.toLowerCase() + : notValue + ; + return (value != notValue); + }, + + // value is not another value (case sensitive) + notExactly: function(value, notValue) { + return (value != notValue); + }, + + // value contains text (insensitive) + contains: function(value, text) { + // escape regex characters + text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); + return (value.search( new RegExp(text, 'i') ) !== -1); + }, + + // value contains text (case sensitive) + containsExactly: function(value, text) { + // escape regex characters + text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); + return (value.search( new RegExp(text) ) !== -1); + }, + + // value contains text (insensitive) + doesntContain: function(value, text) { + // escape regex characters + text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); + return (value.search( new RegExp(text, 'i') ) === -1); + }, + + // value contains text (case sensitive) + doesntContainExactly: function(value, text) { + // escape regex characters + text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); + return (value.search( new RegExp(text) ) === -1); + }, + + // is at least string length + minLength: function(value, requiredLength) { + return (value !== undefined) + ? (value.length >= requiredLength) + : false + ; + }, + + // see rls notes for 2.0.6 (this is a duplicate of minLength) + length: function(value, requiredLength) { + return (value !== undefined) + ? (value.length >= requiredLength) + : false + ; + }, + + // is exactly length + exactLength: function(value, requiredLength) { + return (value !== undefined) + ? (value.length == requiredLength) + : false + ; + }, + + // is less than length + maxLength: function(value, maxLength) { + return (value !== undefined) + ? (value.length <= maxLength) + : false + ; + }, + + // matches another field + match: function(value, identifier) { + var + $form = $(this), + matchingValue + ; + if( $('[data-validate="'+ identifier +'"]').length > 0 ) { + matchingValue = $('[data-validate="'+ identifier +'"]').val(); + } + else if($('#' + identifier).length > 0) { + matchingValue = $('#' + identifier).val(); + } + else if($('[name="' + identifier +'"]').length > 0) { + matchingValue = $('[name="' + identifier + '"]').val(); + } + else if( $('[name="' + identifier +'[]"]').length > 0 ) { + matchingValue = $('[name="' + identifier +'[]"]'); + } + return (matchingValue !== undefined) + ? ( value.toString() == matchingValue.toString() ) + : false + ; + }, + + // different than another field + different: function(value, identifier) { + // use either id or name of field + var + $form = $(this), + matchingValue + ; + if( $('[data-validate="'+ identifier +'"]').length > 0 ) { + matchingValue = $('[data-validate="'+ identifier +'"]').val(); + } + else if($('#' + identifier).length > 0) { + matchingValue = $('#' + identifier).val(); + } + else if($('[name="' + identifier +'"]').length > 0) { + matchingValue = $('[name="' + identifier + '"]').val(); + } + else if( $('[name="' + identifier +'[]"]').length > 0 ) { + matchingValue = $('[name="' + identifier +'[]"]'); + } + return (matchingValue !== undefined) + ? ( value.toString() !== matchingValue.toString() ) + : false + ; + }, + + creditCard: function(cardNumber, cardTypes) { + var + cards = { + visa: { + pattern : /^4/, + length : [16] + }, + amex: { + pattern : /^3[47]/, + length : [15] + }, + mastercard: { + pattern : /^5[1-5]/, + length : [16] + }, + discover: { + pattern : /^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/, + length : [16] + }, + unionPay: { + pattern : /^(62|88)/, + length : [16, 17, 18, 19] + }, + jcb: { + pattern : /^35(2[89]|[3-8][0-9])/, + length : [16] + }, + maestro: { + pattern : /^(5018|5020|5038|6304|6759|676[1-3])/, + length : [12, 13, 14, 15, 16, 17, 18, 19] + }, + dinersClub: { + pattern : /^(30[0-5]|^36)/, + length : [14] + }, + laser: { + pattern : /^(6304|670[69]|6771)/, + length : [16, 17, 18, 19] + }, + visaElectron: { + pattern : /^(4026|417500|4508|4844|491(3|7))/, + length : [16] + } + }, + valid = {}, + validCard = false, + requiredTypes = (typeof cardTypes == 'string') + ? cardTypes.split(',') + : false, + unionPay, + validation + ; + + if(typeof cardNumber !== 'string' || cardNumber.length === 0) { + return; + } + + // verify card types + if(requiredTypes) { + $.each(requiredTypes, function(index, type){ + // verify each card type + validation = cards[type]; + if(validation) { + valid = { + length : ($.inArray(cardNumber.length, validation.length) !== -1), + pattern : (cardNumber.search(validation.pattern) !== -1) + }; + if(valid.length && valid.pattern) { + validCard = true; + } + } + }); + + if(!validCard) { + return false; + } + } + + // skip luhn for UnionPay + unionPay = { + number : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1), + pattern : (cardNumber.search(cards.unionPay.pattern) !== -1) + }; + if(unionPay.number && unionPay.pattern) { + return true; + } + + // verify luhn, adapted from + var + length = cardNumber.length, + multiple = 0, + producedValue = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 2, 4, 6, 8, 1, 3, 5, 7, 9] + ], + sum = 0 + ; + while (length--) { + sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)]; + multiple ^= 1; + } + return (sum % 10 === 0 && sum > 0); + }, + + minCount: function(value, minCount) { + if(minCount == 0) { + return true; + } + if(minCount == 1) { + return (value !== ''); + } + return (value.split(',').length >= minCount); + }, + + exactCount: function(value, exactCount) { + if(exactCount == 0) { + return (value === ''); + } + if(exactCount == 1) { + return (value !== '' && value.search(',') === -1); + } + return (value.split(',').length == exactCount); + }, + + maxCount: function(value, maxCount) { + if(maxCount == 0) { + return false; + } + if(maxCount == 1) { + return (value.search(',') === -1); + } + return (value.split(',').length <= maxCount); + } + } + +}; + +})( jQuery, window, document ); + +/*! + * # Semantic UI 2.2.6 - Accordion + * http://github.com/semantic-org/semantic-ui/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +;(function ($, window, document, undefined) { + +"use strict"; + +window = (typeof window != 'undefined' && window.Math == Math) + ? window + : (typeof self != 'undefined' && self.Math == Math) + ? self + : Function('return this')() +; + +$.fn.accordion = function(parameters) { + var + $allModules = $(this), + + time = new Date().getTime(), + performance = [], + + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + + requestAnimationFrame = window.requestAnimationFrame + || window.mozRequestAnimationFrame + || window.webkitRequestAnimationFrame + || window.msRequestAnimationFrame + || function(callback) { setTimeout(callback, 0); }, + + returnedValue + ; + $allModules + .each(function() { + var + settings = ( $.isPlainObject(parameters) ) + ? $.extend(true, {}, $.fn.accordion.settings, parameters) + : $.extend({}, $.fn.accordion.settings), + + className = settings.className, + namespace = settings.namespace, + selector = settings.selector, + error = settings.error, + + eventNamespace = '.' + namespace, + moduleNamespace = 'module-' + namespace, + moduleSelector = $allModules.selector || '', + + $module = $(this), + $title = $module.find(selector.title), + $content = $module.find(selector.content), + + element = this, + instance = $module.data(moduleNamespace), + observer, + module + ; + + module = { + + initialize: function() { + module.debug('Initializing', $module); + module.bind.events(); + if(settings.observeChanges) { + module.observeChanges(); + } + module.instantiate(); + }, + + instantiate: function() { + instance = module; + $module + .data(moduleNamespace, module) + ; + }, + + destroy: function() { + module.debug('Destroying previous instance', $module); + $module + .off(eventNamespace) + .removeData(moduleNamespace) + ; + }, + + refresh: function() { + $title = $module.find(selector.title); + $content = $module.find(selector.content); + }, + + observeChanges: function() { + if('MutationObserver' in window) { + observer = new MutationObserver(function(mutations) { + module.debug('DOM tree modified, updating selector cache'); + module.refresh(); + }); + observer.observe(element, { + childList : true, + subtree : true + }); + module.debug('Setting up mutation observer', observer); + } + }, + + bind: { + events: function() { + module.debug('Binding delegated events'); + $module + .on(settings.on + eventNamespace, selector.trigger, module.event.click) + ; + } + }, + + event: { + click: function() { + module.toggle.call(this); + } + }, + + toggle: function(query) { + var + $activeTitle = (query !== undefined) + ? (typeof query === 'number') + ? $title.eq(query) + : $(query).closest(selector.title) + : $(this).closest(selector.title), + $activeContent = $activeTitle.next($content), + isAnimating = $activeContent.hasClass(className.animating), + isActive = $activeContent.hasClass(className.active), + isOpen = (isActive && !isAnimating), + isOpening = (!isActive && isAnimating) + ; + module.debug('Toggling visibility of content', $activeTitle); + if(isOpen || isOpening) { + if(settings.collapsible) { + module.close.call($activeTitle); + } + else { + module.debug('Cannot close accordion content collapsing is disabled'); + } + } + else { + module.open.call($activeTitle); + } + }, + + open: function(query) { + var + $activeTitle = (query !== undefined) + ? (typeof query === 'number') + ? $title.eq(query) + : $(query).closest(selector.title) + : $(this).closest(selector.title), + $activeContent = $activeTitle.next($content), + isAnimating = $activeContent.hasClass(className.animating), + isActive = $activeContent.hasClass(className.active), + isOpen = (isActive || isAnimating) + ; + if(isOpen) { + module.debug('Accordion already open, skipping', $activeContent); + return; + } + module.debug('Opening accordion content', $activeTitle); + settings.onOpening.call($activeContent); + if(settings.exclusive) { + module.closeOthers.call($activeTitle); + } + $activeTitle + .addClass(className.active) + ; + $activeContent + .stop(true, true) + .addClass(className.animating) + ; + if(settings.animateChildren) { + if($.fn.transition !== undefined && $module.transition('is supported')) { + $activeContent + .children() + .transition({ + animation : 'fade in', + queue : false, + useFailSafe : true, + debug : settings.debug, + verbose : settings.verbose, + duration : settings.duration + }) + ; + } + else { + $activeContent + .children() + .stop(true, true) + .animate({ + opacity: 1 + }, settings.duration, module.resetOpacity) + ; + } + } + $activeContent + .slideDown(settings.duration, settings.easing, function() { + $activeContent + .removeClass(className.animating) + .addClass(className.active) + ; + module.reset.display.call(this); + settings.onOpen.call(this); + settings.onChange.call(this); + }) + ; + }, + + close: function(query) { + var + $activeTitle = (query !== undefined) + ? (typeof query === 'number') + ? $title.eq(query) + : $(query).closest(selector.title) + : $(this).closest(selector.title), + $activeContent = $activeTitle.next($content), + isAnimating = $activeContent.hasClass(className.animating), + isActive = $activeContent.hasClass(className.active), + isOpening = (!isActive && isAnimating), + isClosing = (isActive && isAnimating) + ; + if((isActive || isOpening) && !isClosing) { + module.debug('Closing accordion content', $activeContent); + settings.onClosing.call($activeContent); + $activeTitle + .removeClass(className.active) + ; + $activeContent + .stop(true, true) + .addClass(className.animating) + ; + if(settings.animateChildren) { + if($.fn.transition !== undefined && $module.transition('is supported')) { + $activeContent + .children() + .transition({ + animation : 'fade out', + queue : false, + useFailSafe : true, + debug : settings.debug, + verbose : settings.verbose, + duration : settings.duration + }) + ; + } + else { + $activeContent + .children() + .stop(true, true) + .animate({ + opacity: 0 + }, settings.duration, module.resetOpacity) + ; + } + } + $activeContent + .slideUp(settings.duration, settings.easing, function() { + $activeContent + .removeClass(className.animating) + .removeClass(className.active) + ; + module.reset.display.call(this); + settings.onClose.call(this); + settings.onChange.call(this); + }) + ; + } + }, + + closeOthers: function(index) { + var + $activeTitle = (index !== undefined) + ? $title.eq(index) + : $(this).closest(selector.title), + $parentTitles = $activeTitle.parents(selector.content).prev(selector.title), + $activeAccordion = $activeTitle.closest(selector.accordion), + activeSelector = selector.title + '.' + className.active + ':visible', + activeContent = selector.content + '.' + className.active + ':visible', + $openTitles, + $nestedTitles, + $openContents + ; + if(settings.closeNested) { + $openTitles = $activeAccordion.find(activeSelector).not($parentTitles); + $openContents = $openTitles.next($content); + } + else { + $openTitles = $activeAccordion.find(activeSelector).not($parentTitles); + $nestedTitles = $activeAccordion.find(activeContent).find(activeSelector).not($parentTitles); + $openTitles = $openTitles.not($nestedTitles); + $openContents = $openTitles.next($content); + } + if( ($openTitles.length > 0) ) { + module.debug('Exclusive enabled, closing other content', $openTitles); + $openTitles + .removeClass(className.active) + ; + $openContents + .removeClass(className.animating) + .stop(true, true) + ; + if(settings.animateChildren) { + if($.fn.transition !== undefined && $module.transition('is supported')) { + $openContents + .children() + .transition({ + animation : 'fade out', + useFailSafe : true, + debug : settings.debug, + verbose : settings.verbose, + duration : settings.duration + }) + ; + } + else { + $openContents + .children() + .stop(true, true) + .animate({ + opacity: 0 + }, settings.duration, module.resetOpacity) + ; + } + } + $openContents + .slideUp(settings.duration , settings.easing, function() { + $(this).removeClass(className.active); + module.reset.display.call(this); + }) + ; + } + }, + + reset: { + + display: function() { + module.verbose('Removing inline display from element', this); + $(this).css('display', ''); + if( $(this).attr('style') === '') { + $(this) + .attr('style', '') + .removeAttr('style') + ; + } + }, + + opacity: function() { + module.verbose('Removing inline opacity from element', this); + $(this).css('opacity', ''); + if( $(this).attr('style') === '') { + $(this) + .attr('style', '') + .removeAttr('style') + ; + } + }, + + }, + + setting: function(name, value) { + module.debug('Changing setting', name, value); + if( $.isPlainObject(name) ) { + $.extend(true, settings, name); + } + else if(value !== undefined) { + if($.isPlainObject(settings[name])) { + $.extend(true, settings[name], value); + } + else { + settings[name] = value; + } + } + else { + return settings[name]; + } + }, + internal: function(name, value) { + module.debug('Changing internal', name, value); + if(value !== undefined) { + if( $.isPlainObject(name) ) { + $.extend(true, module, name); + } + else { + module[name] = value; + } + } + else { + return module[name]; + } + }, + debug: function() { + if(!settings.silent && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.debug.apply(console, arguments); + } + } + }, + verbose: function() { + if(!settings.silent && settings.verbose && settings.debug) { + if(settings.performance) { + module.performance.log(arguments); + } + else { + module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); + module.verbose.apply(console, arguments); + } + } + }, + error: function() { + if(!settings.silent) { + module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); + module.error.apply(console, arguments); + } + }, + performance: { + log: function(message) { + var + currentTime, + executionTime, + previousTime + ; + if(settings.performance) { + currentTime = new Date().getTime(); + previousTime = time || currentTime; + executionTime = currentTime - previousTime; + time = currentTime; + performance.push({ + 'Name' : message[0], + 'Arguments' : [].slice.call(message, 1) || '', + 'Element' : element, + 'Execution Time' : executionTime + }); + } + clearTimeout(module.performance.timer); + module.performance.timer = setTimeout(module.performance.display, 500); + }, + display: function() { + var + title = settings.name + ':', + totalTime = 0 + ; + time = false; + clearTimeout(module.performance.timer); + $.each(performance, function(index, data) { + totalTime += data['Execution Time']; + }); + title += ' ' + totalTime + 'ms'; + if(moduleSelector) { + title += ' \'' + moduleSelector + '\''; + } + if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { + console.groupCollapsed(title); + if(console.table) { + console.table(performance); + } + else { + $.each(performance, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); + }); + } + console.groupEnd(); + } + performance = []; + } + }, + invoke: function(query, passedArguments, context) { + var + object = instance, + maxDepth, + found, + response + ; + passedArguments = passedArguments || queryArguments; + context = element || context; + if(typeof query == 'string' && object !== undefined) { + query = query.split(/[\. ]/); + maxDepth = query.length - 1; + $.each(query, function(depth, value) { + var camelCaseValue = (depth != maxDepth) + ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) + : query + ; + if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { + object = object[camelCaseValue]; + } + else if( object[camelCaseValue] !== undefined ) { + found = object[camelCaseValue]; + return false; + } + else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { + object = object[value]; + } + else if( object[value] !== undefined ) { + found = object[value]; + return false; + } + else { + module.error(error.method, query); + return false; + } + }); + } + if ( $.isFunction( found ) ) { + response = found.apply(context, passedArguments); + } + else if(found !== undefined) { + response = found; + } + if($.isArray(returnedValue)) { + returnedValue.push(response); + } + else if(returnedValue !== undefined) { + returnedValue = [returnedValue, response]; + } + else if(response !== undefined) { + returnedValue = response; + } + return found; + } + }; + if(methodInvoked) { + if(instance === undefined) { + module.initialize(); + } + module.invoke(query); + } + else { + if(instance !== undefined) { + instance.invoke('destroy'); + } + module.initialize(); + } + }) + ; + return (returnedValue !== undefined) + ? returnedValue + : this + ; +}; + +$.fn.accordion.settings = { + + name : 'Accordion', + namespace : 'accordion', + + silent : false, + debug : false, + verbose : false, + performance : true, + + on : 'click', // event on title that opens accordion + + observeChanges : true, // whether accordion should automatically refresh on DOM insertion + + exclusive : true, // whether a single accordion content panel should be open at once + collapsible : true, // whether accordion content can be closed + closeNested : false, // whether nested content should be closed when a panel is closed + animateChildren : true, // whether children opacity should be animated + + duration : 350, // duration of animation + easing : 'easeOutQuad', // easing equation for animation + + + onOpening : function(){}, // callback before open animation + onOpen : function(){}, // callback after open animation + onClosing : function(){}, // callback before closing animation + onClose : function(){}, // callback after closing animation + onChange : function(){}, // callback after closing or opening animation + + error: { + method : 'The method you called is not defined' + }, + + className : { + active : 'active', + animating : 'animating' + }, + + selector : { + accordion : '.accordion', + title : '.title', + trigger : '.title', + content : '.content' + } + +}; + +// Adds easing +$.extend( $.easing, { + easeOutQuad: function (x, t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + } +}); + +})( jQuery, window, document ); + + +/*! + * # Semantic UI 2.2.6 - Checkbox + * http://github.com/semantic-org/semantic-ui/ + * + * + * Released under the MIT license + * http://opensource.org/licenses/MIT + * + */ + +;(function ($, window, document, undefined) { + +"use strict"; + +window = (typeof window != 'undefined' && window.Math == Math) + ? window + : (typeof self != 'undefined' && self.Math == Math) + ? self + : Function('return this')() +; + +$.fn.checkbox = function(parameters) { + var + $allModules = $(this), + moduleSelector = $allModules.selector || '', + + time = new Date().getTime(), + performance = [], + + query = arguments[0], + methodInvoked = (typeof query == 'string'), + queryArguments = [].slice.call(arguments, 1), + returnedValue + ; + + $allModules + .each(function() { + var + settings = $.extend(true, {}, $.fn.checkbox.settings, parameters), + + className = settings.className, + namespace = settings.namespace, + selector = settings.selector, + error = settings.error, + + eventNamespace = '.' + namespace, + moduleNamespace = 'module-' + namespace, + + $module = $(this), + $label = $(this).children(selector.label), + $input = $(this).children(selector.input), + input = $input[0], + + initialLoad = false, + shortcutPressed = false, + instance = $module.data(moduleNamespace), + + observer, + element = this, + module + ; + + module = { + + initialize: function() { + module.verbose('Initializing checkbox', settings); + + module.create.label(); + module.bind.events(); + + module.set.tabbable(); + module.hide.input(); + + module.observeChanges(); + module.instantiate(); + module.setup(); + }, + + instantiate: function() { + module.verbose('Storing instance of module', module); + instance = module; + $module + .data(moduleNamespace, module) + ; + }, + + destroy: function() { + module.verbose('Destroying module'); + module.unbind.events(); + module.show.input(); + $module.removeData(moduleNamespace); + }, + + fix: { + reference: function() { + if( $module.is(selector.input) ) { + module.debug('Behavior called on adjusting invoked element'); + $module = $module.closest(selector.checkbox); + module.refresh(); + } + } + }, + + setup: function() { + module.set.initialLoad(); + if( module.is.indeterminate() ) { + module.debug('Initial value is indeterminate'); + module.indeterminate(); + } + else if( module.is.checked() ) { + module.debug('Initial value is checked'); + module.check(); + } + else { + module.debug('Initial value is unchecked'); + module.uncheck(); + } + module.remove.initialLoad(); + }, + + refresh: function() { + $label = $module.children(selector.label); + $input = $module.children(selector.input); + input = $input[0]; + }, + + hide: { + input: function() { + module.verbose('Modifying z-index to be unselectable'); + $input.addClass(className.hidden); + } + }, + show: { + input: function() { + module.verbose('Modifying z-index to be selectable'); + $input.removeClass(className.hidden); + } + }, + + observeChanges: function() { + if('MutationObserver' in window) { + observer = new MutationObserver(function(mutations) { + module.debug('DOM tree modified, updating selector cache'); + module.refresh(); + }); + observer.observe(element, { + childList : true, + subtree : true + }); + module.debug('Setting up mutation observer', observer); + } + }, + + attachEvents: function(selector, event) { + var + $element = $(selector) + ; + event = $.isFunction(module[event]) + ? module[event] + : module.toggle + ; + if($element.length > 0) { + module.debug('Attaching checkbox events to element', selector, event); + $element + .on('click' + eventNamespace, event) + ; + } + else { + module.error(error.notFound); + } + }, + + event: { + click: function(event) { + var + $target = $(event.target) + ; + if( $target.is(selector.input) ) { + module.verbose('Using default check action on initialized checkbox'); + return; + } + if( $target.is(selector.link) ) { + module.debug('Clicking link inside checkbox, skipping toggle'); + return; + } + module.toggle(); + $input.focus(); + event.preventDefault(); + }, + keydown: function(event) { + var + key = event.which, + keyCode = { + enter : 13, + space : 32, + escape : 27 + } + ; + if(key == keyCode.escape) { + module.verbose('Escape key pressed blurring field'); + $input.blur(); + shortcutPressed = true; + } + else if(!event.ctrlKey && ( key == keyCode.space || key == keyCode.enter) ) { + module.verbose('Enter/space key pressed, toggling checkbox'); + module.toggle(); + shortcutPressed = true; + } + else { + shortcutPressed = false; + } + }, + keyup: function(event) { + if(shortcutPressed) { + event.preventDefault(); + } + } + }, + + check: function() { + if( !module.should.allowCheck() ) { + return; + } + module.debug('Checking checkbox', $input); + module.set.checked(); + if( !module.should.ignoreCallbacks() ) { + settings.onChecked.call(input); + settings.onChange.call(input); + } + }, + + uncheck: function() { + if( !module.should.allowUncheck() ) { + return; + } + module.debug('Unchecking checkbox'); + module.set.unchecked(); + if( !module.should.ignoreCallbacks() ) { + settings.onUnchecked.call(input); + settings.onChange.call(input); + } + }, + + indeterminate: function() { + if( module.should.allowIndeterminate() ) { + module.debug('Checkbox is already indeterminate'); + return; + } + module.debug('Making checkbox indeterminate'); + module.set.indeterminate(); + if( !module.should.ignoreCallbacks() ) { + settings.onIndeterminate.call(input); + settings.onChange.call(input); + } + }, + + determinate: function() { + if( module.should.allowDeterminate() ) { + module.debug('Checkbox is already determinate'); + return; + } + module.debug('Making checkbox determinate'); + module.set.determinate(); + if( !module.should.ignoreCallbacks() ) { + settings.onDeterminate.call(input); + settings.onChange.call(input); + } + }, + + enable: function() { + if( module.is.enabled() ) { + module.debug('Checkbox is already enabled'); + return; + } + module.debug('Enabling checkbox'); + module.set.enabled(); + settings.onEnable.call(input); + // preserve legacy callbacks + settings.onEnabled.call(input); + }, + + disable: function() { + if( module.is.disabled() ) { + module.debug('Checkbox is already disabled'); + return; + } + module.debug('Disabling checkbox'); + module.set.disabled(); + settings.onDisable.call(input); + // preserve legacy callbacks + settings.onDisabled.call(input); + }, + + get: { + radios: function() { + var + name = module.get.name() + ; + return $('input[name="' + name + '"]').closest(selector.checkbox); + }, + otherRadios: function() { + return module.get.radios().not($module); + }, + name: function() { + return $input.attr('name'); + } + }, + + is: { + initialLoad: function() { + return initialLoad; + }, + radio: function() { + return ($input.hasClass(className.radio) || $input.attr('type') == 'radio'); + }, + indeterminate: function() { + return $input.prop('indeterminate') !== undefined && $input.prop('indeterminate'); + }, + checked: function() { + return $input.prop('checked') !== undefined && $input.prop('checked'); + }, + disabled: function() { + return $input.prop('disabled') !== undefined && $input.prop('disabled'); + }, + enabled: function() { + return !module.is.disabled(); + }, + determinate: function() { + return !module.is.indeterminate(); + }, + unchecked: function() { + return !module.is.checked(); + } + }, + + should: { + allowCheck: function() { + if(module.is.determinate() && module.is.checked() && !module.should.forceCallbacks() ) { + module.debug('Should not allow check, checkbox is already checked'); + return false; + } + if(settings.beforeChecked.apply(input) === false) { + module.debug('Should not allow check, beforeChecked cancelled'); + return false; + } + return true; + }, + allowUncheck: function() { + if(module.is.determinate() && module.is.unchecked() && !module.should.forceCallbacks() ) { + module.debug('Should not allow uncheck, checkbox is already unchecked'); + return false; + } + if(settings.beforeUnchecked.apply(input) === false) { + module.debug('Should not allow uncheck, beforeUnchecked cancelled'); + return false; + } + return true; + }, + allowIndeterminate: function() { + if(module.is.indeterminate() && !module.should.forceCallbacks() ) { + module.debug('Should not allow indeterminate, checkbox is already indeterminate'); + return false; + } + if(settings.beforeIndeterminate.apply(input) === false) { + module.debug('Should not allow indeterminate, beforeIndeterminate cancelled'); + return false; + } + return true; + }, + allowDeterminate: function() { + if(module.is.determinate() && !module.should.forceCallbacks() ) { + module.debug('Should not allow determinate, checkbox is already determinate'); + return false; + } + if(settings.beforeDeterminate.apply(input) === false) { + module.debug('Should not allow determinate, beforeDeterminate cancelled'); + return false; + } + return true; + }, + forceCallbacks: function() { + return (module.is.initialLoad() && settings.fireOnInit); + }, + ignoreCallbacks: function() { + return (initialLoad && !settings.fireOnInit); + } + }, + + can: { + change: function() { + return !( $module.hasClass(className.disabled) || $module.hasClass(className.readOnly) || $input.prop('disabled') || $input.prop('readonly') ); + }, + uncheck: function() { + return (typeof settings.uncheckable === 'boolean') + ? settings.uncheckable + : !module.is.radio() + ; + } + }, + + set: { + initialLoad: function() { + initialLoad = true; + }, + checked: function() { + module.verbose('Setting class to checked'); + $module + .removeClass(className.indeterminate) + .addClass(className.checked) + ; + if( module.is.radio() ) { + module.uncheckOthers(); + } + if(!module.is.indeterminate() && module.is.checked()) { + module.debug('Input is already checked, skipping input property change'); + return; + } + module.verbose('Setting state to checked', input); + $input + .prop('indeterminate', false) + .prop('checked', true) + ; + module.trigger.change(); + }, + unchecked: function() { + module.verbose('Removing checked class'); + $module + .removeClass(className.indeterminate) + .removeClass(className.checked) + ; + if(!module.is.indeterminate() && module.is.unchecked() ) { + module.debug('Input is already unchecked'); + return; + } + module.debug('Setting state to unchecked'); + $input + .prop('indeterminate', false) + .prop('checked', false) + ; + module.trigger.change(); + }, + indeterminate: function() { + module.verbose('Setting class to indeterminate'); + $module + .addClass(className.indeterminate) + ; + if( module.is.indeterminate() ) { + module.debug('Input is already indeterminate, skipping input property change'); + return; + } + module.debug('Setting state to indeterminate'); + $input + .prop('indeterminate', true) + ; + module.trigger.change(); + }, + determinate: function() { + module.verbose('Removing indeterminate class'); + $module + .removeClass(className.indeterminate) + ; + if( module.is.determinate() ) { + module.debug('Input is already determinate, skipping input property change'); + return; + } + module.debug('Setting state to determinate'); + $input + .prop('indeterminate', false) + ; + }, + disabled: function() { + module.verbose('Setting class to disabled'); + $module + .addClass(className.disabled) + ; + if( module.is.disabled() ) { + module.debug('Input is already disabled, skipping input property change'); + return; + } + module.debug('Setting state to disabled'); + $input + .prop('disabled', 'disabled') + ; + module.trigger.change(); + }, + enabled: function() { + module.verbose('Removing disabled class'); + $module.removeClass(className.disabled); + if( module.is.enabled() ) { + module.debug('Input is already enabled, skipping input property change'); + return; + } + module.debug('Setting state to enabled'); + $input + .prop('disabled', false) + ; + module.trigger.change(); + }, + tabbable: function() { + module.verbose('Adding tabindex to checkbox'); + if( $input.attr('tabindex') === undefined) { + $input.attr('tabindex', 0); + } + } + }, + + remove: { + initialLoad: function() { + initialLoad = false; + } + }, + + trigger: { + change: function() { + var + events = document.createEvent('HTMLEvents'), + inputElement = $input[0] + ; + if(inputElement) { + module.verbose('Triggering native change event'); + events.initEvent('change', true, false); + inputElement.dispatchEvent(events); + } + } + }, + + + create: { + label: function() { + if($input.prevAll(selector.label).length > 0) { + $input.prev(selector.label).detach().insertAfter($input); + module.debug('Moving existing label', $label); + } + else if( !module.has.label() ) { + $label = $('