├── README.md ├── binary_sensors.yaml ├── configuration.yaml ├── customize.yaml ├── floorplan.yaml ├── frontend.yaml ├── own-floorplan-svg-file-tutorial.md ├── panel_custom.yaml ├── panels └── floorplan.html ├── tutorial_images ├── object_properties.PNG ├── simple_plan.PNG └── workearea_size.PNG └── www └── custom_ui ├── floorplan ├── floorplan.css ├── floorplan.svg ├── ha-floorplan.html └── lib │ ├── jquery-3.2.1.min.js │ ├── moment.min.js │ └── svg-pan-zoom.min.js └── state-card-floorplan.html /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 |
4 |

5 | ⚠️ Please migrate to ha-floorplan ⚠️ 6 |

7 |

8 | ha-floorplan has been replaced with ha-floorplan.

Please check out the new solution, and let us know what you think.

9 |

10 |
13 | 14 | # Floorplan for Home Assistant 15 | 16 | ![floorplan](https://user-images.githubusercontent.com/2073827/27056482-911f2e14-500b-11e7-90f0-44a344c39f85.png) 17 | 18 | ## Background 19 | 20 | Out of the box, the Home Assistant [front end](https://home-assistant.io/docs/frontend/) provides a great way of viewing and interacting with your entities. This project builds on top of that, allowing you to extend the user interface by adding your own visuals. 21 | 22 | With Floorplan for Home Assistant, you can: 23 | 24 | - Integrate with Home Assistant as either a state card or a custom panel 25 | - Display any number of entities (i.e. binary sensors, lights, cameras, etc.) 26 | - Style each entity's states using CSS 27 | - Gradually transition between states using color gradients 28 | - Display the last triggered binary sensor using CSS 29 | - Display hover-over text for each entity 30 | - Call a service or display a popup dialog when an entity is clicked 31 | 32 | Despite its title, Floorplan for Home Assistant can be used as a general purpose user interface for just about anything you want to present in a visual way. 33 | 34 | The concept is quite simple. You create an SVG file and simply add shapes/images to represent your Home Assistant entities. As long as the ids match up, your SVG comes to life and displays your entities' states in real time. Your entities also become clickable, so they act as shortcuts to opening the built-in 'more info' popups that are normally displayed by Home Assistant. This means clicking on a camera icon, for example, opens the Home Assistant 'more info' popup that displays the live camera feed. Hovering over the entitiy displays a tooltip showing all the information related to the entity. 35 | 36 | Although using it as a floorplan is the most common use case, you can go even further and create visuals of real world components. Some examples are: 37 | 38 | - An SVG image of a remote control with each button click triggering a service in Home Assistant 39 | - An SVG image of a Ring doorbell with the sensor and camera mapped to sensors in Home Assistant 40 | - An SVG image of a Logitech Squeezebox media player with the screen text mapped to the state, current song, etc. in Home Assistant 41 | 42 | ## Usage 43 | 44 | To get started, copy the following files from this repo to your Home Assistant directory: 45 | 46 | ``` 47 | www/custom_ui/floorplan/ha-floorplan.html 48 | www/custom_ui/floorplan/floorplan.svg 49 | www/custom_ui/floorplan/floorplan.css 50 | www/custom_ui/floorplan/lib/jquery-3.2.1.min.js 51 | www/custom_ui/floorplan/lib/moment.min.js 52 | www/custom_ui/floorplan/lib/svg-pan-zoom.min.js 53 | ``` 54 | 55 | Although a sample floorplan SVG file is included in this repo, you will want to create your own. See the [appendix](#creating-a-floorplan-svg-file) for more information. 56 | 57 | You then have two options for how you want to the floorplan to appear in Home Assistant: 58 | 59 | - custom state card 60 | - custom panel 61 | 62 | Of course, you can choose to have it displayed in both places. If you have several floorplans to display (i.e. different levels of a house), that is supported too. 63 | 64 | ### Option 1: Floorplan custom state card 65 | 66 | ![image](https://user-images.githubusercontent.com/2073827/27063035-97aa2a6e-5032-11e7-8e8e-79935a19aebf.png) 67 | 68 | To display the floorplan as a custom state card, copy the following file from this repo to your Home Assistant directory: 69 | 70 | ``` 71 | www/custom_ui/state-card-floorplan.html 72 | ``` 73 | 74 | To allow the above file to be served by Home Assistant, add it to the `frontend` section of your Home Assistant configuration: 75 | 76 | ``` 77 | frontend: 78 | extra_html_url: 79 | - /local/custom_ui/state-card-floorplan.html 80 | ``` 81 | 82 | Since Home Assistant requires a single entity to be used as the target for a state card, create a virtual entity to represent the overall floorplan. You can choose any type of entity for this, such as the MQTT binary sensor. Add the following to your Home Assistant configuration: 83 | 84 | ``` 85 | binary_sensor: 86 | - platform: mqtt 87 | state_topic: dummy/floorplan/sensor 88 | name: Floorplan 89 | ``` 90 | 91 | Then, add the following to get Home Assistant to display this new virtual entity using the floorplan custom state card: 92 | 93 | ``` 94 | homeassistant: 95 | customize: 96 | binary_sensor.floorplan: 97 | custom_ui_state_card: state-card-floorplan 98 | config: !include floorplan.yaml 99 | ``` 100 | 101 | To actually display the floorplan custom state card in the front end, add the virtual entity to one of your groups: 102 | 103 | ``` 104 | group: 105 | zones: 106 | name: Zones 107 | entities: 108 | - binary_sensor.floorplan 109 | ``` 110 | 111 | You can also add a 'last motion' entity to keep track of which binary sensor was triggered last. See the [appendix](#adding-a-last-motion-entity-to-your-floorplan) for more information. 112 | 113 | ### Option 2: Floorplan custom panel 114 | 115 | ![image](https://user-images.githubusercontent.com/2073827/27063110-08d3fd82-5033-11e7-85b6-671722608394.png) 116 | 117 | To display the floorplan as a custom panel, copy the following file from this repo to your Home Assistant directory: 118 | 119 | ``` 120 | panels/floorplan.html 121 | ``` 122 | 123 | Then, add the following to your Home Assistant configuration: 124 | 125 | ``` 126 | panel_custom: 127 | - name: floorplan 128 | sidebar_title: Floorplan 129 | sidebar_icon: mdi:home 130 | url_path: floorplan 131 | config: !include floorplan.yaml 132 | ``` 133 | 134 | ### Configure the floorplan 135 | 136 | Whether your floorplan is being displayed as a custom state card or as a custom panel, the same configuration file `floorplan.yaml` is used. This is where you tell Home Assistant which entities you want to display on your floorplan. 137 | 138 | The example `floorplan.yaml` included in this repo shows how to add various entities to your floorplan and style their appearance based on their states. 139 | 140 | At the top of the file, you provide a name for the floorplan, as well as the location of the SVG and CSS files: 141 | 142 | ``` 143 | name: Demo Floorplan 144 | image: /local/custom_ui/floorplan/floorplan.svg 145 | stylesheet: /local/custom_ui/floorplan/floorplan.css 146 | ``` 147 | 148 | If you want the floorplan to display any warnings (i.e. SVG file does not contain required elements), add the following: 149 | 150 | ``` 151 | warnings: 152 | ``` 153 | 154 | If you want to support panning and zooming within your SVG file, add the following: 155 | 156 | ``` 157 | pan_zoom: 158 | ``` 159 | 160 | If you want to hide the main application toolbar and display the floorplan in true fullscreen mode (when used as a custom panel), add the following: 161 | 162 | ``` 163 | hide_app_toolbar: 164 | ``` 165 | 166 | To set the date format displayed in the hover-over text, add the following: 167 | 168 | ``` 169 | date_format: DD-MMM-YYYY 170 | ``` 171 | 172 | If you want to display a 'last motion' entity, you can include that in the next section of the file. You specify the name of the entity, as well as the CSS class used to style its appearance: 173 | 174 | ``` 175 | last_motion_entity: sensor.template_last_motion 176 | last_motion_class: last-motion 177 | ``` 178 | 179 | The remainder of the file is where you add your floorplan groups. These floorplan groups are not to be confused with [Home Assistant entity groups](https://home-assistant.io/components/group) that are used to combine multiple entities into one. 180 | 181 | ``` 182 | groups: 183 | ``` 184 | 185 | You need to place each of your entities into a floorplan group, since configuration is performed at a floorplan group level. The floorplan groups can be given any name, and have no purpose other than to allow for configuration of multiple items in one place. 186 | 187 | If you've already created some Home Assistant entity groups, you can actually include those groups in two different ways: 188 | 189 | - single - the group will be represented as a single entity (`group.pantry_lights` in the example below). These sorts of Home Assistant entity groups get added beneath `entities:`). 190 | 191 | - exploded - the group will be exploded into separate entities (`group.living_area_lights` in the example below). These sorts of Home Assistant entity groups get added beneath `groups:`). 192 | 193 | ``` 194 | - name: Lights 195 | entities: 196 | - light.kitchen 197 | - group.pantry_lights 198 | groups: 199 | - group.living_area_lights 200 | ``` 201 | 202 | In addition to monitoring your entities in real time, you can also trigger actions when your entities are clicked. Below is an example of such an action. Whenever one of the lights in the group is clicked, an action is triggered that calls the Home Assistant 'toggle' service. See the [appendix](#triggering-actions) for more information. 203 | 204 | ``` 205 | - name: Lights 206 | entities: 207 | - light.kitchen 208 | - group.pantry_lights 209 | groups: 210 | - group.living_area_lights 211 | action: 212 | service: toggle 213 | ``` 214 | 215 | Below are some examples of groups, showing how to configure different types of entities in the floorplan. 216 | 217 | #### Sensors 218 | 219 | Below is an example of a 'Sensors' group, showing how to add a temperature sensor (as text) to your floorplan. in the screenshot above, this can be seen as an SVG text element displaying the current temperature (i.e. '9.0°'). 220 | 221 | The sensor's state is displayed using a `text_template`. As you can see, it contains some embedded code that determines which actual text to display. 222 | 223 | The sensor's CSS class is determined dynamically using a `class_template`. In the example below, the CSS class is determined based on the actual temperature value. 224 | 225 | See the [appendix](#using-template-literals-in-your-configuration) for more information on how to use template literals in your configuration. 226 | 227 | ``` 228 | - name: Sensors 229 | entities: 230 | - sensor.melbourne_now 231 | - group.major_city_temp_sensors 232 | text_template: '${entity.state ? entity.state : "unknown"}' 233 | class_template: ' 234 | var temp = parseFloat(entity.state.replace("°", "")); 235 | if (temp < 10) 236 | return "temp-low"; 237 | else if (temp < 30) 238 | return "temp-medium"; 239 | else 240 | return "temp-high"; 241 | ' 242 | ``` 243 | 244 | Below is an example of using dynamic images which are swapped out at runtime, based on the sensor's current state. In the example below, the `sensor.home_dark_sky_icon` entitiy is mapped to a `` in the SVG file with the same id (which simply acts as a placeholder). Whenever the temperature sensor changes state, the `image_template` is evaluated to determine which SVG image should be emebedded within the bounds of the ``. Also you need to make sure that the placeholder is placed directly within the svg (e.g. not in a layer in inkscape) or else the calculated coordinates will be wrong. 245 | 246 | ``` 247 | groups: 248 | 249 | - name: Dark Sky Sensors 250 | entities: 251 | - sensor.home_dark_sky_icon 252 | image_template: ' 253 | var imageName = ""; 254 | 255 | switch (entity.state) { 256 | case "clear-day": 257 | imageName = "day"; 258 | break; 259 | 260 | case "clear-night": 261 | imageName = "night"; 262 | break; 263 | 264 | case "partly-cloudy-day": 265 | imageName = "cloudy-day-1"; 266 | break; 267 | 268 | case "partly-cloudy-night": 269 | imageName = "cloudy-night-1"; 270 | break; 271 | 272 | case "cloudy": 273 | imageName = "cloudy"; 274 | break; 275 | 276 | case "rain": 277 | imageName = "rainy-1"; 278 | break; 279 | 280 | case "snow": 281 | imageName = "snowy-1"; 282 | break; 283 | } 284 | 285 | return "/local/custom_ui/floorplan/images/weather/" + imageName + ".svg"; 286 | ' 287 | ``` 288 | 289 | #### Switches 290 | 291 | Below is an example of a 'Switches' group, showing how to add switches to your floorplan. The appearance of each switch is styled using the appropriate CSS class, based on its current state. 292 | 293 | ``` 294 | - name: Switches 295 | entities: 296 | - switch.doorbell 297 | states: 298 | - state: 'on' 299 | class: 'doorbell-on' 300 | - state: 'off' 301 | class: 'doorbell-off' 302 | action: 303 | domain: switch 304 | service: toggle 305 | ``` 306 | 307 | #### Lights 308 | 309 | Below is an example of a 'Lights' group, showing how to add lights to your floorplan. The appearance of each light is styled using the appropriate CSS class, based on its current state. 310 | 311 | ``` 312 | - name: Lights 313 | entities: 314 | - light.hallway 315 | - light.living_room 316 | - light.patio 317 | states: 318 | - state: 'on' 319 | class: 'light-on' 320 | - state: 'off' 321 | class: 'light-off' 322 | ``` 323 | 324 | #### Alarm Panel 325 | 326 | Below is an example of an 'Alarm Panel' group, showing how to add an alarm panel (as text) to your floorplan. The appearance of the alarm panel is styled using the appropriate CSS class, based on its current state. In the screenshot above, this can be seen as an SVG text element displaying the current alarm status (i.e. 'disarmed'). 327 | 328 | ``` 329 | - name: Alarm Panel 330 | entities: 331 | - alarm_control_panel.alarm 332 | states: 333 | - state: 'armed_away' 334 | class: 'alarm-armed' 335 | - state: 'armed_home' 336 | class: 'alarm-armed' 337 | - state: 'disarmed' 338 | class: 'alarm-disarmed' 339 | ``` 340 | 341 | #### Binary Sensors 342 | 343 | Below is an example of a 'Binary sensors' group, showing how to add binary sensors to your floorplan. The appearance of each binary sensor is styled using the appropriate CSS class, based on its current state. In the screenshot above, these can be seen as SVG paths (i.e. rooms/zones of the house). 344 | 345 | The `state_transitions` section is optional, and allows your binary sensors to visually transition from one state to another, using the fill colors defined in the CSS classes associated with each state. You can specify the duration (in seconds) for the transition from one color to the other. 346 | 347 | ``` 348 | - name: Binary Sensors 349 | entities: 350 | - binary_sensor.front_hallway 351 | - binary_sensor.kitchen 352 | - binary_sensor.master_bedroom 353 | - binary_sensor.theatre_room 354 | states: 355 | - state: 'off' 356 | class: 'info-background' 357 | - state: 'on' 358 | class: 'warning-background' 359 | state_transitions: 360 | - name: On to off 361 | from_state: 'on' 362 | to_state: 'off' 363 | duration: 10 364 | ``` 365 | 366 | #### Cameras 367 | 368 | Below is an example of a 'Cameras' group, showing how to add cameras to your floorplan. The appearance of each camera is styled using the appropriate CSS class, based on its current state. In the screenshot above, these can be seen as camera icons, which were imported from an external SVG image. 369 | 370 | - name: Cameras 371 | entities: 372 | - camera.hallway 373 | - camera.driveway 374 | - camera.front_door 375 | - camera.backyard 376 | states: 377 | - state: 'idle' 378 | class: 'camera-idle' 379 | 380 | #### Media Players 381 | 382 | Below is an example of a 'Media Players' group, showing how to add media players to your floorplan. The appearance of each media player is styled using the appropriate CSS class, based on its current state. In the screenshot above, these can be seen as Logitech Squeezebox icons, which were imported from an external SVG image. 383 | 384 | - name: Media Players 385 | entities: 386 | - media_player.alfresco 387 | - media_player.ensuite 388 | - media_player.salon 389 | states: 390 | - state: 'off' 391 | class: 'squeezebox-off' 392 | - state: 'idle' 393 | class: 'squeezebox-off' 394 | - state: 'paused' 395 | class: 'squeezebox-off' 396 | - state: 'playing' 397 | class: 'squeezebox-on' 398 | 399 | #### Toggling the visibility of entities 400 | 401 | If you'd like to control the visibility of your entities, you can create a layer in your SVG file (using the `` element) that contains the entities you want show/hide, along with a button (using ``, for example) that is actually used to toggle the visiblity. Below is an example of a button `media_players_button` that toggles the visibility of all media players in the floorplan (i.e. those that are contained within the `media_players_layer` layer). The floorplan toggles between the two CSS classes whenever the button is clicked. 402 | 403 | ``` 404 | - name: Media Players 405 | elements: 406 | - media_players_button 407 | action: 408 | domain: class 409 | service: toggle 410 | data: 411 | elements: 412 | - media_players_layer 413 | classes: 414 | - layer-visible 415 | - layer-hidden 416 | default_class: layer-hidden 417 | ``` 418 | 419 | ## Appendix 420 | 421 | ### Creating a floorplan SVG file 422 | 423 | [Inkscape](https://inkscape.org/en/develop/about-svg/) is a free application that lets you create vector images. You can make your floorplan as simple or as detailed as you want. The only requirement is that you create an element (i.e. `rect`, `path`, `text`, etc.) for each entity ( i.e. binary sensor, switch, camera, etc.) you want to display on your floorplan. Each of these elements needs to have its `id` set to the corresponding entity name in Home Assistant. 424 | 425 | For example, below is what the SVG element looks like for a Front Hallway binary sensor. The `id` of the shape is set to the entity name `binary_sensor.front_hallway`. This allows the shape to automatically get hooked up to the right entity when the floorplan is displayed. 426 | 427 | ```html 428 | 430 | ``` 431 | 432 | If you need a good source of SVG files for icons or images, you can check out the following resources : 433 | [Material Design Icons](https://materialdesignicons.com/), [Noun Project](https://thenounproject.com/) and [Flat Icon](http://flaticon.com) 434 | 435 | ### Adding a last motion entity to your floorplan 436 | 437 | As an optional step, you can create a 'last motion' entity to keep track of which binary sensor was triggered last. To do so, add the following: 438 | 439 | ``` 440 | sensor: 441 | - platform: template 442 | sensors: 443 | template_last_motion: 444 | friendly_name: 'Last Motion' 445 | value_template: > 446 | {%- set sensors = [states.binary_sensor.theatre_room, states.binary_sensor.back_hallway, states.binary_sensor.front_hallway, states.binary_sensor.kitchen] %} 447 | {% for sensor in sensors %} 448 | {% if as_timestamp(sensor.last_changed) == as_timestamp(sensors | map(attribute='last_changed') | max) %} 449 | {{ sensor.name }} 450 | {% endif %} 451 | {% endfor %} 452 | ``` 453 | 454 | To actually display the 'last motion' entity', add it to one of your groups: 455 | 456 | ``` 457 | group: 458 | zones: 459 | name: Zones 460 | entities: 461 | - sensor.template_last_motion 462 | - binary_sensor.floorplan 463 | ``` 464 | 465 | ### Using template literals in your configuration 466 | 467 | The settings `text_template`, `class_template`, and `action_template` allow you to inject your own expressions and code using JavaScript [template literals](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals). Within these template literals, you have full access to the entity's state object, which allows you to access other properties such as `last_changed`, `attributes.friendly_name`, etc. The full set of objects available to your template literals is shown below: 468 | 469 | - `entity` - the state object for the current entity 470 | - `entities` - the state objects for all entities 471 | - `hass` - the [hass object](https://home-assistant.io/developers/development_hass_object/) 472 | - `config` - the floorplan configuration 473 | 474 | ``` 475 | - name: Sensors 476 | entities: 477 | - sensor.melbourne_now 478 | text_template: '${entity.state ? entity.state : "unknown"}' 479 | class_template: ' 480 | var temp = parseFloat(entity.state.replace("°", "")); 481 | if (temp < 10) 482 | return "temp-low"; 483 | else if (temp < 30) 484 | return "temp-medium"; 485 | else 486 | return "temp-high"; 487 | ' 488 | ``` 489 | 490 | ### Triggering actions 491 | 492 | Within each group, you can define an `action` that triggers a call to the specified Home Assistant service when an entity is clicked. The `domain` is optional, and defaults to either the domain of the entity being clicked (for regular entities, i.e. 'light'), or to 'homeassistant' (for Home Assistant group entities). 493 | 494 | In its simplest form, an `action` can be used to toggle an enity (or a group of entities, in the case of a Home assistant group). 495 | 496 | ``` 497 | action: 498 | service: toggle 499 | ``` 500 | 501 | You can also explictly set the `domain` if you want to call a service from a particular domain. 502 | 503 | ``` 504 | action: 505 | domain: homeassistant 506 | service: toggle 507 | ``` 508 | The ability to specify a domain means you can kick off just about any service available in Home Assistant (scripts, automations, notifcations, shell commands, TTS, etc.). 509 | 510 | ``` 511 | action: 512 | domain: script 513 | service: sound_frontdoor_chime 514 | ``` 515 | 516 | For services that support additional data, you can include that as well. Below is an example of setting the transition and brightness when switching on a light. 517 | 518 | ``` 519 | action: 520 | domain: light 521 | service: turn_on 522 | data: 523 | transition: 50 524 | brightness: 75 525 | ``` 526 | 527 | When an entity is clicked, it can actually trigger an action on another entity. The example below shows how clicking on a light triggers a different light to be switched on, by supplying the other's light's `entity_id` as part of the action. 528 | 529 | ``` 530 | action: 531 | domain: light 532 | service: turn_on 533 | data: 534 | entity_id: light.some_other_light 535 | transition: 50 536 | brightness: 75 537 | ``` 538 | 539 | For more flexibility, you can use the `data_template` to dynamically generate data required for your `action`. The example below shows how a JSON object is dynamically created and populated with data. Thanks to template literals, you can inject code to evaluate expressions at runtime. Just for the purposes of illustration, the example shows the use of the JavaScript Math.min() function being used in conjunction with another entity's current state. 540 | 541 | ``` 542 | action: 543 | domain: light 544 | service: turn_on 545 | data_template: ' 546 | { 547 | "entity_id": "light.some_other_light", 548 | "brightness": ${Math.min(entities["zone.home"].attributes.radius, 50)} 549 | } 550 | ' 551 | ``` 552 | 553 | ## Troubleshooting 554 | 555 | First of all, check the indentation of the floorplan config. All the examples above show the correct level of indentantion, so make sure that's done before proceedeing further. 556 | 557 | The recommended web browser to use is Google Chrome. Pressing F12 displays the Developer Tools. When you press F5 to reload your floorplan page, the Console pane will show any errors that may have occurred. Also check the Network tab to see if any of the scripts failed to load. Ad-blockers have been known to prevent some scripts from loading. 558 | 559 | If you're not seeing latest changes that you've made, try clearing the web browser cache. This can also be done in the Chrome Developer Tools. Select the Network tab, right click and select Clear browser cache. 560 | 561 | If you're not able to access the floorplan in your web browswer at all, it could be that you've been locked out of Home Assistant due to too many failed login attempts. Check the file `ip_bans.yaml` in the root Home Assistant config directory and remove your IP address if it's in there. 562 | 563 | If you encounter any issues with your entities not appearing, or not correctly showing state changes, firstly make sure that `warnings:` is added to your floorplan config. It will report any SVG elements that are missing, misspelt, etc. 564 | 565 | If you're adding your own CSS classes for styling your entities, make sure you escape the dot character in the id, by prefixing it with a backlash: 566 | 567 | ``` 568 | #light\.hallway:hover { 569 | } 570 | ``` 571 | 572 | ## Resources 573 | 574 | Check out Patrik's tutorial on [how to create a custom floorplan SVG](own-floorplan-svg-file-tutorial.md) 575 | 576 | ## More information 577 | 578 | For discussions and more information, check out the [thread](https://community.home-assistant.io/t/floorplan-for-home-assistant) on the Home Assistant forums. 579 | -------------------------------------------------------------------------------- /binary_sensors.yaml: -------------------------------------------------------------------------------- 1 | - platform: mqtt 2 | state_topic: dummy/floorplan/sensor 3 | name: Floorplan 4 | -------------------------------------------------------------------------------- /configuration.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: !include customize.yaml 3 | 4 | frontend: !include frontend.yaml 5 | panel_custom: !include panel_custom.yaml 6 | binary_sensor: !include binary_sensors.yaml 7 | -------------------------------------------------------------------------------- /customize.yaml: -------------------------------------------------------------------------------- 1 | binary_sensor.floorplan: 2 | custom_ui_state_card: state-card-floorplan 3 | config: !include floorplan.yaml 4 | -------------------------------------------------------------------------------- /floorplan.yaml: -------------------------------------------------------------------------------- 1 | name: Demo Floorplan 2 | image: /local/custom_ui/floorplan/floorplan.svg 3 | stylesheet: /local/custom_ui/floorplan/floorplan.css 4 | 5 | # These options are optional 6 | # warnings: # enable warnings (to find out why things might ot be working correctly) 7 | # pan_zoom: # enable experimental panning / zooming 8 | # hide_app_toolbar: # hide the application toolbar (when used as a custom panel) 9 | # date_format: DD-MMM-YYYY # Date format to use in hover-over text 10 | 11 | last_motion_entity: sensor.template_last_motion 12 | last_motion_class: last-motion 13 | 14 | groups: 15 | 16 | - name: Sensors 17 | entities: 18 | - sensor.dark_sky_temperature 19 | #text_template: '${entity.state ? entity.state : "unknown"}' 20 | # An example of rounding up a number, i.e. a temperature of 90.1 becomes 91 21 | text_template: '${entity.state ? Math.ceil(entity.state) : "undefined"}' 22 | class_template: ' 23 | var temp = parseFloat(entity.state.replace("°", "")); 24 | if (temp < 10) 25 | return "temp-low"; 26 | else if (temp < 30) 27 | return "temp-medium"; 28 | else 29 | return "temp-high"; 30 | ' 31 | 32 | - name: Lights 33 | entities: 34 | - light.hallway 35 | - light.living_room 36 | - light.patio 37 | - group.kitchen_lights 38 | - group.living_room_lights 39 | states: 40 | - state: 'on' 41 | class: 'light-on' 42 | - state: 'off' 43 | class: 'light-off' 44 | action: 45 | domain: homeassistant # This optional parameter allows you to use other services such as homeassistant.toggle like here. 46 | service: toggle 47 | 48 | - name: Switches 49 | entities: 50 | - switch.doorbell 51 | states: 52 | - state: 'on' 53 | class: 'doorbell-on' 54 | - state: 'off' 55 | class: 'doorbell-off' 56 | 57 | - name: NVR 58 | entities: 59 | - binary_sensor.blue_iris_nvr 60 | text_template: '${(entity.state === "on") ? "online" : "offline"}' 61 | states: 62 | - state: 'off' 63 | class: 'danger-text' 64 | - state: 'on' 65 | class: 'success-text' 66 | 67 | - name: Alarm Panel 68 | entities: 69 | - alarm_control_panel.alarm 70 | states: 71 | - state: 'armed_away' 72 | class: 'alarm-armed' 73 | - state: 'armed_home' 74 | class: 'alarm-armed' 75 | - state: 'disarmed' 76 | class: 'alarm-disarmed' 77 | 78 | - name: Binary sensors 79 | entities: 80 | - binary_sensor.front_hallway 81 | - binary_sensor.kitchen 82 | - binary_sensor.master_bedroom 83 | - binary_sensor.theatre_room 84 | states: 85 | - state: 'off' 86 | class: 'info-background' 87 | - state: 'on' 88 | class: 'warning-background' 89 | state_transitions: 90 | - name: On to off 91 | from_state: 'on' 92 | to_state: 'off' 93 | duration: 10 94 | 95 | - name: Cameras 96 | entities: 97 | - camera.hallway 98 | - camera.driveway 99 | - camera.front_door 100 | - camera.backyard 101 | states: 102 | - state: 'idle' 103 | class: 'camera-idle' 104 | 105 | # - name: thermostat_temp 106 | # entities: 107 | # - climate.downstairs 108 | # - climate.upstairs 109 | # text_template: '${entity.attributes.current_temperature ? entity.attributes.current_temperature : "undefined"}' 110 | # 111 | # The above text_template uses extended attributes from the climate.* objects to get current temperature. 112 | 113 | # - name: text_states 114 | # entities: 115 | # - sensor.downstairs_thermostat_humidity 116 | # - sensor.dark_sky_temperature 117 | # - sensor.last_message 118 | # text_template: '${entity.state ? entity.state.replace(/\s{2,}/g,"") : "undefined"}' 119 | # 120 | # The above text_template uses jQuery syntax to search and replace any instance of 2 consecutive (or more) spaces in a string of text. 121 | -------------------------------------------------------------------------------- /frontend.yaml: -------------------------------------------------------------------------------- 1 | extra_html_url: 2 | - /local/custom_ui/state-card-floorplan.html 3 | -------------------------------------------------------------------------------- /own-floorplan-svg-file-tutorial.md: -------------------------------------------------------------------------------- 1 | # Creating your own custom floorplan-file from scratch # 2 | 3 | This is a short guide to creating your own floorplan-file, based on your own home. 4 | 5 | Recommended resources: 6 | - [Inkscape](https://inkscape.org/en/) for editing your floorplan 7 | - [the Noun Project](https://thenounproject.com/) for neat looking custom icons 8 | 9 | ## 1. Get started 10 | Download, install and open [Inkscape](https://inkscape.org/en/). 11 | 12 | ## 1.1 Set the size of the work area 13 | I've set the size of my floorplan-file to match the resolution of a full-screen iPad Air (1024x768). 14 | - Click File > Document Properties 15 | - Ensure the top right corner says "px" as display units. There is another one under "Custom size" that should also read "px". 16 | - Set the width and height of Orientation/custom size to 1024 by 768. 17 | - Make sure scale = 1 18 | - Set view box x, y, width and height to: 0, 0, 1024, 768. 19 | - Close the document properties and save the file as floorplan.svg 20 | 21 | ![floorplan](https://github.com/pkozul/ha-floorplan/blob/master/tutorial_images/workearea_size.PNG) 22 | 23 | 24 | ## 1.2 Draw your building 25 | Start drawing your house/apartment using rectangles. Add two placeholders for lights using the circle tool (or download and insert more classy looking ones from the Noun Project). Also add a text string that we will be using for showing the temperature. These steps have been taken in the image below. 26 | 27 | ![floorplan](https://github.com/pkozul/ha-floorplan/blob/master/tutorial_images/simple_plan.PNG) 28 | 29 | ## 1.3 Link the items to entities in Home Assistant 30 | To link an object in the floorplan-file to Home Assistant, you first set its object id to the name of the entity in Home Assistant. There are two lights: ```light.hall_1``` and ```light.hall_2``` as well as a temperature sensor ```sensor.forecastio_apparent_temperature```. 31 | - To link an entity, right click one of the circles and select "Object properties". You will see something along "ID: xzyy3212". 32 | - Set the ID to light.hall_1 as shown in the image below. Click "Set". 33 | - Click outside of the circle and click inside it again and make sure Inkscape hasn't added "_" to the end of the ID. 34 | - Do the same for the other light and also with the text. 35 | 36 | ![floorplan](https://github.com/pkozul/ha-floorplan/blob/master/tutorial_images/object_properties.PNG) 37 | 38 | ## 1.4 Add the necessary config to your floorplan.yaml-file 39 | Add the following lines to your floorplan.yaml file: 40 | 41 | ``` 42 | - name: temp_forecastio 43 | entities: 44 | - sensor.forecastio_apparent_temperature 45 | text_template: '${entity.state ? Math.ceil(entity.state) + "°": "undefined"}' 46 | class_template: 'return "static-temp";' 47 | 48 | - name: Lights 49 | entities: 50 | - light.hall_1 51 | - light.hall_2 52 | states: 53 | - state: 'on' 54 | class: 'light-on' 55 | - state: 'off' 56 | class: 'light-off' 57 | ``` 58 | 59 | ## 1.5 Upload your floorplan 60 | Upload and overwrite the floorplan.svg file with your own, customized, file. 61 | 62 | ## 1.6 Restart Home Assistant 63 | You need to do this in order to pick up the changes you made to the floorplan.yaml-file. 64 | -------------------------------------------------------------------------------- /panel_custom.yaml: -------------------------------------------------------------------------------- 1 | - name: floorplan 2 | sidebar_title: Floorplan 3 | sidebar_icon: mdi:home 4 | url_path: floorplan 5 | config: !include floorplan.yaml 6 | -------------------------------------------------------------------------------- /panels/floorplan.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 33 | 34 | 35 | 36 | 78 | -------------------------------------------------------------------------------- /tutorial_images/object_properties.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkozul/ha-floorplan/1a3f3d781b1d4b85de54e938338a363c5babfec5/tutorial_images/object_properties.PNG -------------------------------------------------------------------------------- /tutorial_images/simple_plan.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkozul/ha-floorplan/1a3f3d781b1d4b85de54e938338a363c5babfec5/tutorial_images/simple_plan.PNG -------------------------------------------------------------------------------- /tutorial_images/workearea_size.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkozul/ha-floorplan/1a3f3d781b1d4b85de54e938338a363c5babfec5/tutorial_images/workearea_size.PNG -------------------------------------------------------------------------------- /www/custom_ui/floorplan/floorplan.css: -------------------------------------------------------------------------------- 1 | /* SVG shapes */ 2 | 3 | svg, svg * { 4 | vector-effect: non-scaling-stroke !important; 5 | pointer-events: all !important; 6 | } 7 | 8 | /* Hover over */ 9 | 10 | .entity:hover { 11 | stroke: #03A9F4 !important; 12 | stroke-width: 1px !important; 13 | stroke-opacity: 1 !important; 14 | } 15 | 16 | /* Bootstrap succsss */ 17 | 18 | .success-text { 19 | fill: #3c763d !important; 20 | } 21 | 22 | .success-background, .success-text-background { 23 | fill: #dff0d8 !important; 24 | fill-opacity: 1 !important; 25 | stroke: #d6e9c6 !important; 26 | stroke-width: 1px !important; 27 | } 28 | 29 | /* Bootstrap info */ 30 | 31 | .info-text { 32 | fill: #31708f !important; 33 | } 34 | 35 | .info-background, .info-text-background { 36 | fill: #d9edf7 !important; 37 | fill-opacity: 1 !important; 38 | stroke: #bce8f1 !important; 39 | stroke-width: 1px !important; 40 | } 41 | 42 | /* Bootstrap warning */ 43 | 44 | .warning-text { 45 | fill: #8a6d3b !important; 46 | } 47 | 48 | .warning-background, .warning-text-background { 49 | fill: #fcf8e3 !important; 50 | fill-opacity: 1 !important; 51 | stroke: #faebcc !important; 52 | stroke-width: 1px !important; 53 | } 54 | 55 | /* Bootstrap danger */ 56 | 57 | .danger-text { 58 | fill: #a94442 !important; 59 | } 60 | 61 | .danger-background, .danger-text-background { 62 | fill: #f2dede !important; 63 | fill-opacity: 1 !important; 64 | stroke: #ebccd1 !important; 65 | stroke-width: 1px !important; 66 | } 67 | 68 | /* Last motion entity */ 69 | 70 | .last-motion { 71 | stroke: #808080 !important; 72 | stroke-width: 1px !important; 73 | stroke-opacity: 1 !important; 74 | } 75 | 76 | /* Alarm Panel */ 77 | 78 | .alarm-disarmed { 79 | fill: #3c763d !important; 80 | } 81 | 82 | .alarm-armed { 83 | fill: #8a6d3b !important; 84 | } 85 | 86 | /* Camera */ 87 | 88 | .camera-idle { 89 | /* fill: #B9CEF7 !important; */ 90 | fill: #6FAECE !important; 91 | } 92 | 93 | /* Light */ 94 | 95 | .light-off { 96 | fill: #C3B7F4 !important; 97 | } 98 | 99 | .light-on { 100 | fill: #F8D2B9 !important; 101 | } 102 | 103 | /* Doorbell */ 104 | 105 | .doorbell-off { 106 | fill: #C3B7F4 !important; 107 | } 108 | 109 | .doorbell-on { 110 | fill: #F8D2B9 !important; 111 | } 112 | 113 | /* Temperature sensor */ 114 | 115 | .temp-very-low-background { 116 | fill: #d9edf7 !important; 117 | fill-opacity: 1 !important; 118 | } 119 | 120 | .temp-below-average-background { 121 | fill: #dcefe8 !important; 122 | fill-opacity: 1 !important; 123 | } 124 | 125 | .temp-average-background { 126 | fill: #dff0d8 !important; 127 | fill-opacity: 1 !important; 128 | } 129 | 130 | .temp-very-high-background { 131 | fill: #f2dede !important; 132 | fill-opacity: 1 !important; 133 | } 134 | 135 | /* Media player */ 136 | 137 | .squeezebox-off { 138 | fill: #8AA8A7 !important; 139 | } 140 | 141 | .squeezebox-on { 142 | fill: #2baaa6 !important; 143 | } 144 | -------------------------------------------------------------------------------- /www/custom_ui/floorplan/floorplan.svg: -------------------------------------------------------------------------------- 1 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 62 | 68 | 69 | 76 | 84 | 90 | 96 | 101 | 107 | 113 | 118 | 123 | 128 | 133 | 138 | 143 | 148 | 154 | 159 | 164 | 169 | 174 | 179 | 184 | 190 | 196 | 201 | 207 | 212 | 217 | 222 | 227 | 233 | 238 | 243 | 248 | 253 | 258 | 263 | 268 | 273 | 278 | 283 | 289 | 295 | 300 | 305 | 310 | 315 | 320 | 325 | 330 | 335 | 341 | 346 | 351 | 356 | 362 | 367 | 372 | 377 | 383 | 388 | 393 | 398 | 403 | 408 | 413 | 418 | 423 | 428 | 433 | 438 | 443 | 448 | 453 | 458 | 463 | 468 | 473 | 479 | 484 | 489 | 495 | 501 | 507 | 513 | 519 | 524 | 530 | 535 | 541 | 546 | 551 | 556 | 561 | 566 | 571 | 576 | 581 | 586 | 591 | 596 | 602 | 607 | 613 | 618 | Alarm: 628 | NVR: 638 | Melbourne: 648 | ----------- 659 | ----------- 670 | ----------- 681 | 685 | 692 | 693 | 697 | 704 | 705 | 709 | 716 | 717 | 721 | 728 | 729 | 733 | 740 | 741 | 745 | 752 | 753 | 757 | 764 | 765 | 769 | 776 | 777 | 781 | 788 | 789 | 797 | 802 | 804 | 809 | 833 | 862 | 863 | 864 | 869 | 871 | 876 | 900 | 929 | 930 | 931 | 936 | 938 | 943 | 967 | 996 | 997 | 998 | 999 | -------------------------------------------------------------------------------- /www/custom_ui/floorplan/ha-floorplan.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 65 | 66 | 67 | 68 | 977 | -------------------------------------------------------------------------------- /www/custom_ui/floorplan/lib/moment.min.js: -------------------------------------------------------------------------------- 1 | //! moment.js 2 | //! version : 2.18.1 3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 4 | //! license : MIT 5 | //! momentjs.com 6 | !function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return sd.apply(null,arguments)}function b(a){sd=a}function c(a){return a instanceof Array||"[object Array]"===Object.prototype.toString.call(a)}function d(a){return null!=a&&"[object Object]"===Object.prototype.toString.call(a)}function e(a){var b;for(b in a)return!1;return!0}function f(a){return void 0===a}function g(a){return"number"==typeof a||"[object Number]"===Object.prototype.toString.call(a)}function h(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function i(a,b){var c,d=[];for(c=0;c0)for(c=0;c0?"future":"past"];return z(c)?c(b):c.replace(/%s/i,b)}function J(a,b){var c=a.toLowerCase();Hd[c]=Hd[c+"s"]=Hd[b]=a}function K(a){return"string"==typeof a?Hd[a]||Hd[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)j(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(a,b){Id[a]=b}function N(a){var b=[];for(var c in a)b.push({unit:c,priority:Id[c]});return b.sort(function(a,b){return a.priority-b.priority}),b}function O(b,c){return function(d){return null!=d?(Q(this,b,d),a.updateOffset(this,c),this):P(this,b)}}function P(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function Q(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)}function R(a){return a=K(a),z(this[a])?this[a]():this}function S(a,b){if("object"==typeof a){a=L(a);for(var c=N(a),d=0;d=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function U(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Md[a]=e),b&&(Md[b[0]]=function(){return T(e.apply(this,arguments),b[1],b[2])}),c&&(Md[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function V(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function W(a){var b,c,d=a.match(Jd);for(b=0,c=d.length;b=0&&Kd.test(a);)a=a.replace(Kd,c),Kd.lastIndex=0,d-=1;return a}function Z(a,b,c){ce[a]=z(b)?b:function(a,d){return a&&c?c:b}}function $(a,b){return j(ce,a)?ce[a](b._strict,b._locale):new RegExp(_(a))}function _(a){return aa(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function aa(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function ba(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),g(b)&&(d=function(a,c){c[b]=u(a)}),c=0;c=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function ta(a){var b=new Date(Date.UTC.apply(null,arguments));return a<100&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b}function ua(a,b,c){var d=7+b-c,e=(7+ta(a,0,d).getUTCDay()-b)%7;return-e+d-1}function va(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ua(a,d,e),j=1+7*(b-1)+h+i;return j<=0?(f=a-1,g=pa(f)+j):j>pa(a)?(f=a+1,g=j-pa(a)):(f=a,g=j),{year:f,dayOfYear:g}}function wa(a,b,c){var d,e,f=ua(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return g<1?(e=a.year()-1,d=g+xa(e,b,c)):g>xa(a.year(),b,c)?(d=g-xa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function xa(a,b,c){var d=ua(a,b,c),e=ua(a+1,b,c);return(pa(a)-d+e)/7}function ya(a){return wa(a,this._week.dow,this._week.doy).week}function za(){return this._week.dow}function Aa(){return this._week.doy}function Ba(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function Ca(a){var b=wa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function Da(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function Ea(a,b){return"string"==typeof a?b.weekdaysParse(a)%7||7:isNaN(a)?null:a}function Fa(a,b){return a?c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]:c(this._weekdays)?this._weekdays:this._weekdays.standalone}function Ga(a){return a?this._weekdaysShort[a.day()]:this._weekdaysShort}function Ha(a){return a?this._weekdaysMin[a.day()]:this._weekdaysMin}function Ia(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],d=0;d<7;++d)f=l([2e3,1]).day(d),this._minWeekdaysParse[d]=this.weekdaysMin(f,"").toLocaleLowerCase(),this._shortWeekdaysParse[d]=this.weekdaysShort(f,"").toLocaleLowerCase(),this._weekdaysParse[d]=this.weekdays(f,"").toLocaleLowerCase();return c?"dddd"===b?(e=ne.call(this._weekdaysParse,g),e!==-1?e:null):"ddd"===b?(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:null):(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:null):"dddd"===b?(e=ne.call(this._weekdaysParse,g),e!==-1?e:(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:null))):"ddd"===b?(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:(e=ne.call(this._weekdaysParse,g),e!==-1?e:(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:null))):(e=ne.call(this._minWeekdaysParse,g),e!==-1?e:(e=ne.call(this._weekdaysParse,g),e!==-1?e:(e=ne.call(this._shortWeekdaysParse,g),e!==-1?e:null)))}function Ja(a,b,c){var d,e,f;if(this._weekdaysParseExact)return Ia.call(this,a,b,c);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;d<7;d++){if(e=l([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}}function Ka(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Da(a,this.localeData()),this.add(a-b,"d")):b}function La(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Ma(a){if(!this.isValid())return null!=a?this:NaN;if(null!=a){var b=Ea(a,this.localeData());return this.day(this.day()%7?b:b-7)}return this.day()||7}function Na(a){return this._weekdaysParseExact?(j(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysStrictRegex:this._weekdaysRegex):(j(this,"_weekdaysRegex")||(this._weekdaysRegex=ye),this._weekdaysStrictRegex&&a?this._weekdaysStrictRegex:this._weekdaysRegex)}function Oa(a){return this._weekdaysParseExact?(j(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(j(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ze),this._weekdaysShortStrictRegex&&a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Pa(a){return this._weekdaysParseExact?(j(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(j(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Ae),this._weekdaysMinStrictRegex&&a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Qa(){function a(a,b){return b.length-a.length}var b,c,d,e,f,g=[],h=[],i=[],j=[];for(b=0;b<7;b++)c=l([2e3,1]).day(b),d=this.weekdaysMin(c,""),e=this.weekdaysShort(c,""),f=this.weekdays(c,""),g.push(d),h.push(e),i.push(f),j.push(d),j.push(e),j.push(f);for(g.sort(a),h.sort(a),i.sort(a),j.sort(a),b=0;b<7;b++)h[b]=aa(h[b]),i[b]=aa(i[b]),j[b]=aa(j[b]);this._weekdaysRegex=new RegExp("^("+j.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+g.join("|")+")","i")}function Ra(){return this.hours()%12||12}function Sa(){return this.hours()||24}function Ta(a,b){U(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Ua(a,b){return b._meridiemParse}function Va(a){return"p"===(a+"").toLowerCase().charAt(0)}function Wa(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Xa(a){return a?a.toLowerCase().replace("_","-"):a}function Ya(a){for(var b,c,d,e,f=0;f0;){if(d=Za(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&v(e,c,!0)>=b-1)break;b--}f++}return null}function Za(a){var b=null;if(!Fe[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Be._abbr,require("./locale/"+a),$a(b)}catch(a){}return Fe[a]}function $a(a,b){var c;return a&&(c=f(b)?bb(a):_a(a,b),c&&(Be=c)),Be._abbr}function _a(a,b){if(null!==b){var c=Ee;if(b.abbr=a,null!=Fe[a])y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),c=Fe[a]._config;else if(null!=b.parentLocale){if(null==Fe[b.parentLocale])return Ge[b.parentLocale]||(Ge[b.parentLocale]=[]),Ge[b.parentLocale].push({name:a,config:b}),null;c=Fe[b.parentLocale]._config}return Fe[a]=new C(B(c,b)),Ge[a]&&Ge[a].forEach(function(a){_a(a.name,a.config)}),$a(a),Fe[a]}return delete Fe[a],null}function ab(a,b){if(null!=b){var c,d=Ee;null!=Fe[a]&&(d=Fe[a]._config),b=B(d,b),c=new C(b),c.parentLocale=Fe[a],Fe[a]=c,$a(a)}else null!=Fe[a]&&(null!=Fe[a].parentLocale?Fe[a]=Fe[a].parentLocale:null!=Fe[a]&&delete Fe[a]);return Fe[a]}function bb(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Be;if(!c(a)){if(b=Za(a))return b;a=[a]}return Ya(a)}function cb(){return Ad(Fe)}function db(a){var b,c=a._a;return c&&n(a).overflow===-2&&(b=c[fe]<0||c[fe]>11?fe:c[ge]<1||c[ge]>ea(c[ee],c[fe])?ge:c[he]<0||c[he]>24||24===c[he]&&(0!==c[ie]||0!==c[je]||0!==c[ke])?he:c[ie]<0||c[ie]>59?ie:c[je]<0||c[je]>59?je:c[ke]<0||c[ke]>999?ke:-1,n(a)._overflowDayOfYear&&(bge)&&(b=ge),n(a)._overflowWeeks&&b===-1&&(b=le),n(a)._overflowWeekday&&b===-1&&(b=me),n(a).overflow=b),a}function eb(a){var b,c,d,e,f,g,h=a._i,i=He.exec(h)||Ie.exec(h);if(i){for(n(a).iso=!0,b=0,c=Ke.length;b10?"YYYY ":"YY "),f="HH:mm"+(c[4]?":ss":""),c[1]){var l=new Date(c[2]),m=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][l.getDay()];if(c[1].substr(0,3)!==m)return n(a).weekdayMismatch=!0,void(a._isValid=!1)}switch(c[5].length){case 2:0===i?h=" +0000":(i=k.indexOf(c[5][1].toUpperCase())-12,h=(i<0?" -":" +")+(""+i).replace(/^-?/,"0").match(/..$/)[0]+"00");break;case 4:h=j[c[5]];break;default:h=j[" GMT"]}c[5]=h,a._i=c.splice(1).join(""),g=" ZZ",a._f=d+e+f+g,lb(a),n(a).rfc2822=!0}else a._isValid=!1}function gb(b){var c=Me.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(eb(b),void(b._isValid===!1&&(delete b._isValid,fb(b),b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b)))))}function hb(a,b,c){return null!=a?a:null!=b?b:c}function ib(b){var c=new Date(a.now());return b._useUTC?[c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()]:[c.getFullYear(),c.getMonth(),c.getDate()]}function jb(a){var b,c,d,e,f=[];if(!a._d){for(d=ib(a),a._w&&null==a._a[ge]&&null==a._a[fe]&&kb(a),null!=a._dayOfYear&&(e=hb(a._a[ee],d[ee]),(a._dayOfYear>pa(e)||0===a._dayOfYear)&&(n(a)._overflowDayOfYear=!0),c=ta(e,0,a._dayOfYear),a._a[fe]=c.getUTCMonth(),a._a[ge]=c.getUTCDate()),b=0;b<3&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;b<7;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[he]&&0===a._a[ie]&&0===a._a[je]&&0===a._a[ke]&&(a._nextDay=!0,a._a[he]=0),a._d=(a._useUTC?ta:sa).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[he]=24)}}function kb(a){var b,c,d,e,f,g,h,i;if(b=a._w,null!=b.GG||null!=b.W||null!=b.E)f=1,g=4,c=hb(b.GG,a._a[ee],wa(tb(),1,4).year),d=hb(b.W,1),e=hb(b.E,1),(e<1||e>7)&&(i=!0);else{f=a._locale._week.dow,g=a._locale._week.doy;var j=wa(tb(),f,g);c=hb(b.gg,a._a[ee],j.year),d=hb(b.w,j.week),null!=b.d?(e=b.d,(e<0||e>6)&&(i=!0)):null!=b.e?(e=b.e+f,(b.e<0||b.e>6)&&(i=!0)):e=f}d<1||d>xa(c,f,g)?n(a)._overflowWeeks=!0:null!=i?n(a)._overflowWeekday=!0:(h=va(c,d,e,f,g),a._a[ee]=h.year,a._dayOfYear=h.dayOfYear)}function lb(b){if(b._f===a.ISO_8601)return void eb(b);if(b._f===a.RFC_2822)return void fb(b);b._a=[],n(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Y(b._f,b._locale).match(Jd)||[],c=0;c0&&n(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),Md[f]?(d?n(b).empty=!1:n(b).unusedTokens.push(f),da(f,d,b)):b._strict&&!d&&n(b).unusedTokens.push(f);n(b).charsLeftOver=i-j,h.length>0&&n(b).unusedInput.push(h),b._a[he]<=12&&n(b).bigHour===!0&&b._a[he]>0&&(n(b).bigHour=void 0),n(b).parsedDateParts=b._a.slice(0),n(b).meridiem=b._meridiem,b._a[he]=mb(b._locale,b._a[he],b._meridiem),jb(b),db(b)}function mb(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&b<12&&(b+=12),d||12!==b||(b=0),b):b}function nb(a){var b,c,d,e,f;if(0===a._f.length)return n(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;ethis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ob(){if(!f(this._isDSTShifted))return this._isDSTShifted;var a={};if(q(a,this),a=qb(a),a._a){var b=a._isUTC?l(a._a):tb(a._a);this._isDSTShifted=this.isValid()&&v(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Pb(){return!!this.isValid()&&!this._isUTC}function Qb(){return!!this.isValid()&&this._isUTC}function Rb(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function Sb(a,b){var c,d,e,f=a,h=null;return Bb(a)?f={ms:a._milliseconds,d:a._days,M:a._months}:g(a)?(f={},b?f[b]=a:f.milliseconds=a):(h=Te.exec(a))?(c="-"===h[1]?-1:1,f={y:0,d:u(h[ge])*c,h:u(h[he])*c,m:u(h[ie])*c,s:u(h[je])*c,ms:u(Cb(1e3*h[ke]))*c}):(h=Ue.exec(a))?(c="-"===h[1]?-1:1,f={y:Tb(h[2],c),M:Tb(h[3],c),w:Tb(h[4],c),d:Tb(h[5],c),h:Tb(h[6],c),m:Tb(h[7],c),s:Tb(h[8],c)}):null==f?f={}:"object"==typeof f&&("from"in f||"to"in f)&&(e=Vb(tb(f.from),tb(f.to)),f={},f.ms=e.milliseconds,f.M=e.months),d=new Ab(f),Bb(a)&&j(a,"_locale")&&(d._locale=a._locale),d}function Tb(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Ub(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Vb(a,b){var c;return a.isValid()&&b.isValid()?(b=Fb(b,a),a.isBefore(b)?c=Ub(a,b):(c=Ub(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}}function Wb(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(y(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Sb(c,d),Xb(this,e,a),this}}function Xb(b,c,d,e){var f=c._milliseconds,g=Cb(c._days),h=Cb(c._months);b.isValid()&&(e=null==e||e,f&&b._d.setTime(b._d.valueOf()+f*d),g&&Q(b,"Date",P(b,"Date")+g*d),h&&ja(b,P(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function Yb(a,b){var c=a.diff(b,"days",!0);return c<-6?"sameElse":c<-1?"lastWeek":c<0?"lastDay":c<1?"sameDay":c<2?"nextDay":c<7?"nextWeek":"sameElse"}function Zb(b,c){var d=b||tb(),e=Fb(d,this).startOf("day"),f=a.calendarFormat(this,e)||"sameElse",g=c&&(z(c[f])?c[f].call(this,d):c[f]);return this.format(g||this.localeData().calendar(f,this,tb(d)))}function $b(){return new r(this)}function _b(a,b){var c=s(a)?a:tb(a);return!(!this.isValid()||!c.isValid())&&(b=K(f(b)?"millisecond":b),"millisecond"===b?this.valueOf()>c.valueOf():c.valueOf()9999?X(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):z(Date.prototype.toISOString)?this.toDate().toISOString():X(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function jc(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var a="moment",b="";this.isLocal()||(a=0===this.utcOffset()?"moment.utc":"moment.parseZone",b="Z");var c="["+a+'("]',d=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",e="-MM-DD[T]HH:mm:ss.SSS",f=b+'[")]';return this.format(c+d+e+f)}function kc(b){b||(b=this.isUtc()?a.defaultFormatUtc:a.defaultFormat);var c=X(this,b);return this.localeData().postformat(c)}function lc(a,b){return this.isValid()&&(s(a)&&a.isValid()||tb(a).isValid())?Sb({to:this,from:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function mc(a){return this.from(tb(),a)}function nc(a,b){return this.isValid()&&(s(a)&&a.isValid()||tb(a).isValid())?Sb({from:this,to:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function oc(a){return this.to(tb(),a)}function pc(a){var b;return void 0===a?this._locale._abbr:(b=bb(a),null!=b&&(this._locale=b),this)}function qc(){return this._locale}function rc(a){switch(a=K(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":case"date":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a&&this.weekday(0),"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this}function sc(a){return a=K(a),void 0===a||"millisecond"===a?this:("date"===a&&(a="day"),this.startOf(a).add(1,"isoWeek"===a?"week":a).subtract(1,"ms"))}function tc(){return this._d.valueOf()-6e4*(this._offset||0)}function uc(){return Math.floor(this.valueOf()/1e3)}function vc(){return new Date(this.valueOf())}function wc(){var a=this;return[a.year(),a.month(),a.date(),a.hour(),a.minute(),a.second(),a.millisecond()]}function xc(){var a=this;return{years:a.year(),months:a.month(),date:a.date(),hours:a.hours(),minutes:a.minutes(),seconds:a.seconds(),milliseconds:a.milliseconds()}}function yc(){return this.isValid()?this.toISOString():null}function zc(){return o(this)}function Ac(){ 7 | return k({},n(this))}function Bc(){return n(this).overflow}function Cc(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Dc(a,b){U(0,[a,a.length],0,b)}function Ec(a){return Ic.call(this,a,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function Fc(a){return Ic.call(this,a,this.isoWeek(),this.isoWeekday(),1,4)}function Gc(){return xa(this.year(),1,4)}function Hc(){var a=this.localeData()._week;return xa(this.year(),a.dow,a.doy)}function Ic(a,b,c,d,e){var f;return null==a?wa(this,d,e).year:(f=xa(a,d,e),b>f&&(b=f),Jc.call(this,a,b,c,d,e))}function Jc(a,b,c,d,e){var f=va(a,b,c,d,e),g=ta(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this}function Kc(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Lc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function Mc(a,b){b[ke]=u(1e3*("0."+a))}function Nc(){return this._isUTC?"UTC":""}function Oc(){return this._isUTC?"Coordinated Universal Time":""}function Pc(a){return tb(1e3*a)}function Qc(){return tb.apply(null,arguments).parseZone()}function Rc(a){return a}function Sc(a,b,c,d){var e=bb(),f=l().set(d,b);return e[c](f,a)}function Tc(a,b,c){if(g(a)&&(b=a,a=void 0),a=a||"",null!=b)return Sc(a,b,c,"month");var d,e=[];for(d=0;d<12;d++)e[d]=Sc(a,d,c,"month");return e}function Uc(a,b,c,d){"boolean"==typeof a?(g(b)&&(c=b,b=void 0),b=b||""):(b=a,c=b,a=!1,g(b)&&(c=b,b=void 0),b=b||"");var e=bb(),f=a?e._week.dow:0;if(null!=c)return Sc(b,(c+f)%7,d,"day");var h,i=[];for(h=0;h<7;h++)i[h]=Sc(b,(h+f)%7,d,"day");return i}function Vc(a,b){return Tc(a,b,"months")}function Wc(a,b){return Tc(a,b,"monthsShort")}function Xc(a,b,c){return Uc(a,b,c,"weekdays")}function Yc(a,b,c){return Uc(a,b,c,"weekdaysShort")}function Zc(a,b,c){return Uc(a,b,c,"weekdaysMin")}function $c(){var a=this._data;return this._milliseconds=df(this._milliseconds),this._days=df(this._days),this._months=df(this._months),a.milliseconds=df(a.milliseconds),a.seconds=df(a.seconds),a.minutes=df(a.minutes),a.hours=df(a.hours),a.months=df(a.months),a.years=df(a.years),this}function _c(a,b,c,d){var e=Sb(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function ad(a,b){return _c(this,a,b,1)}function bd(a,b){return _c(this,a,b,-1)}function cd(a){return a<0?Math.floor(a):Math.ceil(a)}function dd(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||f<=0&&g<=0&&h<=0||(f+=864e5*cd(fd(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=t(f/1e3),i.seconds=a%60,b=t(a/60),i.minutes=b%60,c=t(b/60),i.hours=c%24,g+=t(c/24),e=t(ed(g)),h+=e,g-=cd(fd(e)),d=t(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function ed(a){return 4800*a/146097}function fd(a){return 146097*a/4800}function gd(a){if(!this.isValid())return NaN;var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+ed(b),"month"===a?c:c/12;switch(b=this._days+Math.round(fd(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function hd(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*u(this._months/12):NaN}function id(a){return function(){return this.as(a)}}function jd(a){return a=K(a),this.isValid()?this[a+"s"]():NaN}function kd(a){return function(){return this.isValid()?this._data[a]:NaN}}function ld(){return t(this.days()/7)}function md(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function nd(a,b,c){var d=Sb(a).abs(),e=uf(d.as("s")),f=uf(d.as("m")),g=uf(d.as("h")),h=uf(d.as("d")),i=uf(d.as("M")),j=uf(d.as("y")),k=e<=vf.ss&&["s",e]||e0,k[4]=c,md.apply(null,k)}function od(a){return void 0===a?uf:"function"==typeof a&&(uf=a,!0)}function pd(a,b){return void 0!==vf[a]&&(void 0===b?vf[a]:(vf[a]=b,"s"===a&&(vf.ss=b-1),!0))}function qd(a){if(!this.isValid())return this.localeData().invalidDate();var b=this.localeData(),c=nd(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function rd(){if(!this.isValid())return this.localeData().invalidDate();var a,b,c,d=wf(this._milliseconds)/1e3,e=wf(this._days),f=wf(this._months);a=t(d/60),b=t(a/60),d%=60,a%=60,c=t(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(m<0?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var sd,td;td=Array.prototype.some?Array.prototype.some:function(a){for(var b=Object(this),c=b.length>>>0,d=0;d68?1900:2e3)};var te=O("FullYear",!0);U("w",["ww",2],"wo","week"),U("W",["WW",2],"Wo","isoWeek"),J("week","w"),J("isoWeek","W"),M("week",5),M("isoWeek",5),Z("w",Sd),Z("ww",Sd,Od),Z("W",Sd),Z("WW",Sd,Od),ca(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=u(a)});var ue={dow:0,doy:6};U("d",0,"do","day"),U("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),U("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),U("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),U("e",0,0,"weekday"),U("E",0,0,"isoWeekday"),J("day","d"),J("weekday","e"),J("isoWeekday","E"),M("day",11),M("weekday",11),M("isoWeekday",11),Z("d",Sd),Z("e",Sd),Z("E",Sd),Z("dd",function(a,b){return b.weekdaysMinRegex(a)}),Z("ddd",function(a,b){return b.weekdaysShortRegex(a)}),Z("dddd",function(a,b){return b.weekdaysRegex(a)}),ca(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict);null!=e?b.d=e:n(c).invalidWeekday=a}),ca(["d","e","E"],function(a,b,c,d){b[d]=u(a)});var ve="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),we="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ye=be,ze=be,Ae=be;U("H",["HH",2],0,"hour"),U("h",["hh",2],0,Ra),U("k",["kk",2],0,Sa),U("hmm",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)}),U("hmmss",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),U("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),U("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ta("a",!0),Ta("A",!1),J("hour","h"),M("hour",13),Z("a",Ua),Z("A",Ua),Z("H",Sd),Z("h",Sd),Z("k",Sd),Z("HH",Sd,Od),Z("hh",Sd,Od),Z("kk",Sd,Od),Z("hmm",Td),Z("hmmss",Ud),Z("Hmm",Td),Z("Hmmss",Ud),ba(["H","HH"],he),ba(["k","kk"],function(a,b,c){var d=u(a);b[he]=24===d?0:d}),ba(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),ba(["h","hh"],function(a,b,c){b[he]=u(a),n(c).bigHour=!0}),ba("hmm",function(a,b,c){var d=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d)),n(c).bigHour=!0}),ba("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d,2)),b[je]=u(a.substr(e)),n(c).bigHour=!0}),ba("Hmm",function(a,b,c){var d=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d))}),ba("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[he]=u(a.substr(0,d)),b[ie]=u(a.substr(d,2)),b[je]=u(a.substr(e))});var Be,Ce=/[ap]\.?m?\.?/i,De=O("Hours",!0),Ee={calendar:Bd,longDateFormat:Cd,invalidDate:Dd,ordinal:Ed,dayOfMonthOrdinalParse:Fd,relativeTime:Gd,months:pe,monthsShort:qe,week:ue,weekdays:ve,weekdaysMin:xe,weekdaysShort:we,meridiemParse:Ce},Fe={},Ge={},He=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ie=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Je=/Z|[+-]\d\d(?::?\d\d)?/,Ke=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Le=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Me=/^\/?Date\((\-?\d+)/i,Ne=/^((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d?\d\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(?:\d\d)?\d\d\s)(\d\d:\d\d)(\:\d\d)?(\s(?:UT|GMT|[ECMP][SD]T|[A-IK-Za-ik-z]|[+-]\d{4}))$/;a.createFromInputFallback=x("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),a.ISO_8601=function(){},a.RFC_2822=function(){};var Oe=x("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var a=tb.apply(null,arguments);return this.isValid()&&a.isValid()?athis?this:a:p()}),Qe=function(){return Date.now?Date.now():+new Date},Re=["year","quarter","month","week","day","hour","minute","second","millisecond"];Db("Z",":"),Db("ZZ",""),Z("Z",_d),Z("ZZ",_d),ba(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Eb(_d,a)});var Se=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var Te=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Ue=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;Sb.fn=Ab.prototype,Sb.invalid=zb;var Ve=Wb(1,"add"),We=Wb(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Xe=x("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});U(0,["gg",2],0,function(){return this.weekYear()%100}),U(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Dc("gggg","weekYear"),Dc("ggggg","weekYear"),Dc("GGGG","isoWeekYear"),Dc("GGGGG","isoWeekYear"),J("weekYear","gg"),J("isoWeekYear","GG"),M("weekYear",1),M("isoWeekYear",1),Z("G",Zd),Z("g",Zd),Z("GG",Sd,Od),Z("gg",Sd,Od),Z("GGGG",Wd,Qd),Z("gggg",Wd,Qd),Z("GGGGG",Xd,Rd),Z("ggggg",Xd,Rd),ca(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=u(a)}),ca(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),U("Q",0,"Qo","quarter"),J("quarter","Q"),M("quarter",7),Z("Q",Nd),ba("Q",function(a,b){b[fe]=3*(u(a)-1)}),U("D",["DD",2],"Do","date"),J("date","D"),M("date",9),Z("D",Sd),Z("DD",Sd,Od),Z("Do",function(a,b){return a?b._dayOfMonthOrdinalParse||b._ordinalParse:b._dayOfMonthOrdinalParseLenient}),ba(["D","DD"],ge),ba("Do",function(a,b){b[ge]=u(a.match(Sd)[0],10)});var Ye=O("Date",!0);U("DDD",["DDDD",3],"DDDo","dayOfYear"),J("dayOfYear","DDD"),M("dayOfYear",4),Z("DDD",Vd),Z("DDDD",Pd),ba(["DDD","DDDD"],function(a,b,c){c._dayOfYear=u(a)}),U("m",["mm",2],0,"minute"),J("minute","m"),M("minute",14),Z("m",Sd),Z("mm",Sd,Od),ba(["m","mm"],ie);var Ze=O("Minutes",!1);U("s",["ss",2],0,"second"),J("second","s"),M("second",15),Z("s",Sd),Z("ss",Sd,Od),ba(["s","ss"],je);var $e=O("Seconds",!1);U("S",0,0,function(){return~~(this.millisecond()/100)}),U(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),U(0,["SSS",3],0,"millisecond"),U(0,["SSSS",4],0,function(){return 10*this.millisecond()}),U(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),U(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),U(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),U(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),U(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),J("millisecond","ms"),M("millisecond",16),Z("S",Vd,Nd),Z("SS",Vd,Od),Z("SSS",Vd,Pd);var _e;for(_e="SSSS";_e.length<=9;_e+="S")Z(_e,Yd);for(_e="S";_e.length<=9;_e+="S")ba(_e,Mc);var af=O("Milliseconds",!1);U("z",0,0,"zoneAbbr"),U("zz",0,0,"zoneName");var bf=r.prototype;bf.add=Ve,bf.calendar=Zb,bf.clone=$b,bf.diff=fc,bf.endOf=sc,bf.format=kc,bf.from=lc,bf.fromNow=mc,bf.to=nc,bf.toNow=oc,bf.get=R,bf.invalidAt=Bc,bf.isAfter=_b,bf.isBefore=ac,bf.isBetween=bc,bf.isSame=cc,bf.isSameOrAfter=dc,bf.isSameOrBefore=ec,bf.isValid=zc,bf.lang=Xe,bf.locale=pc,bf.localeData=qc,bf.max=Pe,bf.min=Oe,bf.parsingFlags=Ac,bf.set=S,bf.startOf=rc,bf.subtract=We,bf.toArray=wc,bf.toObject=xc,bf.toDate=vc,bf.toISOString=ic,bf.inspect=jc,bf.toJSON=yc,bf.toString=hc,bf.unix=uc,bf.valueOf=tc,bf.creationData=Cc,bf.year=te,bf.isLeapYear=ra,bf.weekYear=Ec,bf.isoWeekYear=Fc,bf.quarter=bf.quarters=Kc,bf.month=ka,bf.daysInMonth=la,bf.week=bf.weeks=Ba,bf.isoWeek=bf.isoWeeks=Ca,bf.weeksInYear=Hc,bf.isoWeeksInYear=Gc,bf.date=Ye,bf.day=bf.days=Ka,bf.weekday=La,bf.isoWeekday=Ma,bf.dayOfYear=Lc,bf.hour=bf.hours=De,bf.minute=bf.minutes=Ze,bf.second=bf.seconds=$e,bf.millisecond=bf.milliseconds=af,bf.utcOffset=Hb,bf.utc=Jb,bf.local=Kb,bf.parseZone=Lb,bf.hasAlignedHourOffset=Mb,bf.isDST=Nb,bf.isLocal=Pb,bf.isUtcOffset=Qb,bf.isUtc=Rb,bf.isUTC=Rb,bf.zoneAbbr=Nc,bf.zoneName=Oc,bf.dates=x("dates accessor is deprecated. Use date instead.",Ye),bf.months=x("months accessor is deprecated. Use month instead",ka),bf.years=x("years accessor is deprecated. Use year instead",te),bf.zone=x("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Ib),bf.isDSTShifted=x("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Ob);var cf=C.prototype;cf.calendar=D,cf.longDateFormat=E,cf.invalidDate=F,cf.ordinal=G,cf.preparse=Rc,cf.postformat=Rc,cf.relativeTime=H,cf.pastFuture=I,cf.set=A,cf.months=fa,cf.monthsShort=ga,cf.monthsParse=ia,cf.monthsRegex=na,cf.monthsShortRegex=ma,cf.week=ya,cf.firstDayOfYear=Aa,cf.firstDayOfWeek=za,cf.weekdays=Fa,cf.weekdaysMin=Ha,cf.weekdaysShort=Ga,cf.weekdaysParse=Ja,cf.weekdaysRegex=Na,cf.weekdaysShortRegex=Oa,cf.weekdaysMinRegex=Pa,cf.isPM=Va,cf.meridiem=Wa,$a("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===u(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=x("moment.lang is deprecated. Use moment.locale instead.",$a),a.langData=x("moment.langData is deprecated. Use moment.localeData instead.",bb);var df=Math.abs,ef=id("ms"),ff=id("s"),gf=id("m"),hf=id("h"),jf=id("d"),kf=id("w"),lf=id("M"),mf=id("y"),nf=kd("milliseconds"),of=kd("seconds"),pf=kd("minutes"),qf=kd("hours"),rf=kd("days"),sf=kd("months"),tf=kd("years"),uf=Math.round,vf={ss:44,s:45,m:45,h:22,d:26,M:11},wf=Math.abs,xf=Ab.prototype;return xf.isValid=yb,xf.abs=$c,xf.add=ad,xf.subtract=bd,xf.as=gd,xf.asMilliseconds=ef,xf.asSeconds=ff,xf.asMinutes=gf,xf.asHours=hf,xf.asDays=jf,xf.asWeeks=kf,xf.asMonths=lf,xf.asYears=mf,xf.valueOf=hd,xf._bubble=dd,xf.get=jd,xf.milliseconds=nf,xf.seconds=of,xf.minutes=pf,xf.hours=qf,xf.days=rf,xf.weeks=ld,xf.months=sf,xf.years=tf,xf.humanize=qd,xf.toISOString=rd,xf.toString=rd,xf.toJSON=rd,xf.locale=pc,xf.localeData=qc,xf.toIsoString=x("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",rd),xf.lang=Xe,U("X",0,0,"unix"),U("x",0,0,"valueOf"),Z("x",Zd),Z("X",ae),ba("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),ba("x",function(a,b,c){c._d=new Date(u(a))}),a.version="2.18.1",b(tb),a.fn=bf,a.min=vb,a.max=wb,a.now=Qe,a.utc=l,a.unix=Pc,a.months=Vc,a.isDate=h,a.locale=$a,a.invalid=p,a.duration=Sb,a.isMoment=s,a.weekdays=Xc,a.parseZone=Qc,a.localeData=bb,a.isDuration=Bb,a.monthsShort=Wc,a.weekdaysMin=Zc,a.defineLocale=_a,a.updateLocale=ab,a.locales=cb,a.weekdaysShort=Yc,a.normalizeUnits=K,a.relativeTimeRounding=od,a.relativeTimeThreshold=pd,a.calendarFormat=Yb,a.prototype=bf,a}); 8 | -------------------------------------------------------------------------------- /www/custom_ui/floorplan/lib/svg-pan-zoom.min.js: -------------------------------------------------------------------------------- 1 | // svg-pan-zoom v3.5.1 2 | // https://github.com/ariutta/svg-pan-zoom 3 | !function t(e,n,o){function i(r,a){if(!n[r]){if(!e[r]){var l="function"==typeof require&&require;if(!a&&l)return l(r,!0);if(s)return s(r,!0);var u=new Error("Cannot find module '"+r+"'");throw u.code="MODULE_NOT_FOUND",u}var h=n[r]={exports:{}};e[r][0].call(h.exports,function(t){var n=e[r][1][t];return i(n?n:t)},h,h.exports,t,e,n,o)}return n[r].exports}for(var s="function"==typeof require&&require,r=0;r=0;o--)this.eventListeners.hasOwnProperty(n[o])&&delete this.eventListeners[n[o]]}for(var i in this.eventListeners)(this.options.eventsListenerElement||this.svg).addEventListener(i,this.eventListeners[i],!1);this.options.mouseWheelZoomEnabled&&(this.options.mouseWheelZoomEnabled=!1,this.enableMouseWheelZoom())},l.prototype.enableMouseWheelZoom=function(){if(!this.options.mouseWheelZoomEnabled){var t=this;this.wheelListener=function(e){return t.handleMouseWheel(e)},o.on(this.options.eventsListenerElement||this.svg,this.wheelListener,!1),this.options.mouseWheelZoomEnabled=!0}},l.prototype.disableMouseWheelZoom=function(){this.options.mouseWheelZoomEnabled&&(o.off(this.options.eventsListenerElement||this.svg,this.wheelListener,!1),this.options.mouseWheelZoomEnabled=!1)},l.prototype.handleMouseWheel=function(t){if(this.options.zoomEnabled&&"none"===this.state){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1);var e=t.deltaY||1,n=Date.now()-this.lastMouseWheelEventTime,o=3+Math.max(0,30-n);this.lastMouseWheelEventTime=Date.now(),"deltaMode"in t&&0===t.deltaMode&&t.wheelDelta&&(e=0===t.deltaY?0:Math.abs(t.wheelDelta)/t.deltaY),e=e>-.3&&.3>e?e:(e>0?1:-1)*Math.log(Math.abs(e)+10)/o;var i=this.svg.getScreenCTM().inverse(),s=r.getEventPoint(t,this.svg).matrixTransform(i),a=Math.pow(1+this.options.zoomScaleSensitivity,-1*e);this.zoomAtPoint(a,s)}},l.prototype.zoomAtPoint=function(t,e,n){var o=this.viewport.getOriginalState();n?(t=Math.max(this.options.minZoom*o.zoom,Math.min(this.options.maxZoom*o.zoom,t)),t/=this.getZoom()):this.getZoom()*tthis.options.maxZoom*o.zoom&&(t=this.options.maxZoom*o.zoom/this.getZoom());var i=this.viewport.getCTM(),s=e.matrixTransform(i.inverse()),r=this.svg.createSVGMatrix().translate(s.x,s.y).scale(t).translate(-s.x,-s.y),a=i.multiply(r);a.a!==i.a&&this.viewport.setCTM(a)},l.prototype.zoom=function(t,e){this.zoomAtPoint(t,r.getSvgCenterPoint(this.svg,this.width,this.height),e)},l.prototype.publicZoom=function(t,e){e&&(t=this.computeFromRelativeZoom(t)),this.zoom(t,e)},l.prototype.publicZoomAtPoint=function(t,e,n){if(n&&(t=this.computeFromRelativeZoom(t)),"SVGPoint"!==s.getType(e)){if(!("x"in e&&"y"in e))throw new Error("Given point is invalid");e=r.createSVGPoint(this.svg,e.x,e.y)}this.zoomAtPoint(t,e,n)},l.prototype.getZoom=function(){return this.viewport.getZoom()},l.prototype.getRelativeZoom=function(){return this.viewport.getRelativeZoom()},l.prototype.computeFromRelativeZoom=function(t){return t*this.viewport.getOriginalState().zoom},l.prototype.resetZoom=function(){var t=this.viewport.getOriginalState();this.zoom(t.zoom,!0)},l.prototype.resetPan=function(){this.pan(this.viewport.getOriginalState())},l.prototype.reset=function(){this.resetZoom(),this.resetPan()},l.prototype.handleDblClick=function(t){if(this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),this.options.controlIconsEnabled){var e=t.target.getAttribute("class")||"";if(e.indexOf("svg-pan-zoom-control")>-1)return!1}var n;n=t.shiftKey?1/(2*(1+this.options.zoomScaleSensitivity)):2*(1+this.options.zoomScaleSensitivity);var o=r.getEventPoint(t,this.svg).matrixTransform(this.svg.getScreenCTM().inverse());this.zoomAtPoint(n,o)},l.prototype.handleMouseDown=function(t,e){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),s.mouseAndTouchNormalize(t,this.svg),this.options.dblClickZoomEnabled&&s.isDblClick(t,e)?this.handleDblClick(t):(this.state="pan",this.firstEventCTM=this.viewport.getCTM(),this.stateOrigin=r.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()))},l.prototype.handleMouseMove=function(t){if(this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&this.options.panEnabled){var e=r.getEventPoint(t,this.svg).matrixTransform(this.firstEventCTM.inverse()),n=this.firstEventCTM.translate(e.x-this.stateOrigin.x,e.y-this.stateOrigin.y);this.viewport.setCTM(n)}},l.prototype.handleMouseUp=function(t){this.options.preventMouseEventsDefault&&(t.preventDefault?t.preventDefault():t.returnValue=!1),"pan"===this.state&&(this.state="none")},l.prototype.fit=function(){var t=this.viewport.getViewBox(),e=Math.min(this.width/t.width,this.height/t.height);this.zoom(e,!0)},l.prototype.contain=function(){var t=this.viewport.getViewBox(),e=Math.max(this.width/t.width,this.height/t.height);this.zoom(e,!0)},l.prototype.center=function(){var t=this.viewport.getViewBox(),e=.5*(this.width-(t.width+2*t.x)*this.getZoom()),n=.5*(this.height-(t.height+2*t.y)*this.getZoom());this.getPublicInstance().pan({x:e,y:n})},l.prototype.updateBBox=function(){this.viewport.simpleViewBoxCache()},l.prototype.pan=function(t){var e=this.viewport.getCTM();e.e=t.x,e.f=t.y,this.viewport.setCTM(e)},l.prototype.panBy=function(t){var e=this.viewport.getCTM();e.e+=t.x,e.f+=t.y,this.viewport.setCTM(e)},l.prototype.getPan=function(){var t=this.viewport.getState();return{x:t.x,y:t.y}},l.prototype.resize=function(){var t=r.getBoundingClientRectNormalized(this.svg);this.width=t.width,this.height=t.height;var e=this.viewport;e.options.width=this.width,e.options.height=this.height,e.processCTM(),this.options.controlIconsEnabled&&(this.getPublicInstance().disableControlIcons(),this.getPublicInstance().enableControlIcons())},l.prototype.destroy=function(){var t=this;this.beforeZoom=null,this.onZoom=null,this.beforePan=null,this.onPan=null,this.onUpdatedCTM=null,null!=this.options.customEventsHandler&&this.options.customEventsHandler.destroy({svgElement:this.svg,eventsListenerElement:this.options.eventsListenerElement,instance:this.getPublicInstance()});for(var e in this.eventListeners)(this.options.eventsListenerElement||this.svg).removeEventListener(e,this.eventListeners[e],!1);this.disableMouseWheelZoom(),this.getPublicInstance().disableControlIcons(),this.reset(),h=h.filter(function(e){return e.svg!==t.svg}),delete this.options,delete this.viewport,delete this.publicInstance,delete this.pi,this.getPublicInstance=function(){return null}},l.prototype.getPublicInstance=function(){var t=this;return this.publicInstance||(this.publicInstance=this.pi={enablePan:function(){return t.options.panEnabled=!0,t.pi},disablePan:function(){return t.options.panEnabled=!1,t.pi},isPanEnabled:function(){return!!t.options.panEnabled},pan:function(e){return t.pan(e),t.pi},panBy:function(e){return t.panBy(e),t.pi},getPan:function(){return t.getPan()},setBeforePan:function(e){return t.options.beforePan=null===e?null:s.proxy(e,t.publicInstance),t.pi},setOnPan:function(e){return t.options.onPan=null===e?null:s.proxy(e,t.publicInstance),t.pi},enableZoom:function(){return t.options.zoomEnabled=!0,t.pi},disableZoom:function(){return t.options.zoomEnabled=!1,t.pi},isZoomEnabled:function(){return!!t.options.zoomEnabled},enableControlIcons:function(){return t.options.controlIconsEnabled||(t.options.controlIconsEnabled=!0,i.enable(t)),t.pi},disableControlIcons:function(){return t.options.controlIconsEnabled&&(t.options.controlIconsEnabled=!1,i.disable(t)),t.pi},isControlIconsEnabled:function(){return!!t.options.controlIconsEnabled},enableDblClickZoom:function(){return t.options.dblClickZoomEnabled=!0,t.pi},disableDblClickZoom:function(){return t.options.dblClickZoomEnabled=!1,t.pi},isDblClickZoomEnabled:function(){return!!t.options.dblClickZoomEnabled},enableMouseWheelZoom:function(){return t.enableMouseWheelZoom(),t.pi},disableMouseWheelZoom:function(){return t.disableMouseWheelZoom(),t.pi},isMouseWheelZoomEnabled:function(){return!!t.options.mouseWheelZoomEnabled},setZoomScaleSensitivity:function(e){return t.options.zoomScaleSensitivity=e,t.pi},setMinZoom:function(e){return t.options.minZoom=e,t.pi},setMaxZoom:function(e){return t.options.maxZoom=e,t.pi},setBeforeZoom:function(e){return t.options.beforeZoom=null===e?null:s.proxy(e,t.publicInstance),t.pi},setOnZoom:function(e){return t.options.onZoom=null===e?null:s.proxy(e,t.publicInstance),t.pi},zoom:function(e){return t.publicZoom(e,!0),t.pi},zoomBy:function(e){return t.publicZoom(e,!1),t.pi},zoomAtPoint:function(e,n){return t.publicZoomAtPoint(e,n,!0),t.pi},zoomAtPointBy:function(e,n){return t.publicZoomAtPoint(e,n,!1),t.pi},zoomIn:function(){return this.zoomBy(1+t.options.zoomScaleSensitivity),t.pi},zoomOut:function(){return this.zoomBy(1/(1+t.options.zoomScaleSensitivity)),t.pi},getZoom:function(){return t.getRelativeZoom()},setOnUpdatedCTM:function(e){return t.options.onUpdatedCTM=null===e?null:s.proxy(e,t.publicInstance),t.pi},resetZoom:function(){return t.resetZoom(),t.pi},resetPan:function(){return t.resetPan(),t.pi},reset:function(){return t.reset(),t.pi},fit:function(){return t.fit(),t.pi},contain:function(){return t.contain(),t.pi},center:function(){return t.center(),t.pi},updateBBox:function(){return t.updateBBox(),t.pi},resize:function(){return t.resize(),t.pi},getSizes:function(){return{width:t.width,height:t.height,realZoom:t.getZoom(),viewBox:t.viewport.getViewBox()}},destroy:function(){return t.destroy(),t.pi}}),this.publicInstance};var h=[],c=function(t,e){var n=s.getSvg(t);if(null===n)return null;for(var o=h.length-1;o>=0;o--)if(h[o].svg===n)return h[o].instance.getPublicInstance();return h.push({svg:n,instance:new l(n,e)}),h[h.length-1].instance.getPublicInstance()};e.exports=c},{"./control-icons":2,"./shadow-viewport":3,"./svg-utilities":5,"./uniwheel":6,"./utilities":7}],5:[function(t,e,n){var o=t("./utilities"),i="unknown";document.documentMode&&(i="ie"),e.exports={svgNS:"http://www.w3.org/2000/svg",xmlNS:"http://www.w3.org/XML/1998/namespace",xmlnsNS:"http://www.w3.org/2000/xmlns/",xlinkNS:"http://www.w3.org/1999/xlink",evNS:"http://www.w3.org/2001/xml-events",getBoundingClientRectNormalized:function(t){if(t.clientWidth&&t.clientHeight)return{width:t.clientWidth,height:t.clientHeight};if(t.getBoundingClientRect())return t.getBoundingClientRect();throw new Error("Cannot get BoundingClientRect for SVG.")},getOrCreateViewport:function(t,e){var n=null;if(n=o.isElement(e)?e:t.querySelector(e),!n){var i=Array.prototype.slice.call(t.childNodes||t.children).filter(function(t){return"defs"!==t.nodeName&&"#text"!==t.nodeName});1===i.length&&"g"===i[0].nodeName&&null===i[0].getAttribute("transform")&&(n=i[0])}if(!n){var s="viewport-"+(new Date).toISOString().replace(/\D/g,"");n=document.createElementNS(this.svgNS,"g"),n.setAttribute("id",s);var r=t.childNodes||t.children;if(r&&r.length>0)for(var a=r.length;a>0;a--)"defs"!==r[r.length-a].nodeName&&n.appendChild(r[r.length-a]);t.appendChild(n)}var l=[];return n.getAttribute("class")&&(l=n.getAttribute("class").split(" ")),~l.indexOf("svg-pan-zoom_viewport")||(l.push("svg-pan-zoom_viewport"),n.setAttribute("class",l.join(" "))),n},setupSvgAttributes:function(t){if(t.setAttribute("xmlns",this.svgNS),t.setAttributeNS(this.xmlnsNS,"xmlns:xlink",this.xlinkNS),t.setAttributeNS(this.xmlnsNS,"xmlns:ev",this.evNS),null!==t.parentNode){var e=t.getAttribute("style")||"";-1===e.toLowerCase().indexOf("overflow")&&t.setAttribute("style","overflow: hidden; "+e)}},internetExplorerRedisplayInterval:300,refreshDefsGlobal:o.throttle(function(){for(var t=document.querySelectorAll("defs"),e=t.length,n=0;e>n;n++){var o=t[n];o.parentNode.insertBefore(o,o)}},this.internetExplorerRedisplayInterval),setCTM:function(t,e,n){var o=this,s="matrix("+e.a+","+e.b+","+e.c+","+e.d+","+e.e+","+e.f+")";t.setAttributeNS(null,"transform",s),"transform"in t.style?t.style.transform=s:"-ms-transform"in t.style?t.style["-ms-transform"]=s:"-webkit-transform"in t.style&&(t.style["-webkit-transform"]=s),"ie"===i&&n&&(n.parentNode.insertBefore(n,n),window.setTimeout(function(){o.refreshDefsGlobal()},o.internetExplorerRedisplayInterval))},getEventPoint:function(t,e){var n=e.createSVGPoint();return o.mouseAndTouchNormalize(t,e),n.x=t.clientX,n.y=t.clientY,n},getSvgCenterPoint:function(t,e,n){return this.createSVGPoint(t,e/2,n/2)},createSVGPoint:function(t,e,n){var o=t.createSVGPoint();return o.x=e,o.y=n,o}}},{"./utilities":7}],6:[function(t,e,n){e.exports=function(){function t(t,e,n){var o=function(t){!t&&(t=window.event);var n={originalEvent:t,target:t.target||t.srcElement,type:"wheel",deltaMode:"MozMousePixelScroll"==t.type?0:1,deltaX:0,delatZ:0,preventDefault:function(){t.preventDefault?t.preventDefault():t.returnValue=!1}};return"mousewheel"==u?(n.deltaY=-1/40*t.wheelDelta,t.wheelDeltaX&&(n.deltaX=-1/40*t.wheelDeltaX)):n.deltaY=t.detail,e(n)};return c.push({element:t,fn:o,capture:n}),o}function e(t,e){for(var n=0;nn&&10>o}return!1},now:Date.now||function(){return(new Date).getTime()},throttle:function(t,e,n){var o,i,s,r=this,a=null,l=0;n||(n={});var u=function(){l=n.leading===!1?0:r.now(),a=null,s=t.apply(o,i),a||(o=i=null)};return function(){var h=r.now();l||n.leading!==!1||(l=h);var c=e-(h-l);return o=this,i=arguments,0>=c||c>e?(clearTimeout(a),a=null,l=h,s=t.apply(o,i),a||(o=i=null)):a||n.trailing===!1||(a=setTimeout(u,c)),s}},createRequestAnimationFrame:function(t){var e=null;return"auto"!==t&&60>t&&t>1&&(e=Math.floor(1e3/t)),null===e?window.requestAnimationFrame||o(33):o(e)}}},{}]},{},[1]); 4 | -------------------------------------------------------------------------------- /www/custom_ui/state-card-floorplan.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 31 | --------------------------------------------------------------------------------